git.lucas.co / cce-system-interface
system settings
git clone https://git.lucas.co/cce-system-interface.git

commit6f5b5d047d821f4553aa708e3e1c2f8c8ce4671c
parente85a30d1a1
authorLucas Galante <[email protected]>
date2026-06-04 13:36
Implement Google OAuth Credentials Configuration UI under Accounts Settings tab

 src/main.rs           | 194 ++++++++++++++++++++++++++++++--------------------
 src/pages/accounts.rs | 124 +++++++++++++++++++++++++++++++-
 2 files changed, 238 insertions(+), 80 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index f9d05aa..af2af1a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -623,6 +623,8 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
         self.app.accounts.password_box.clear_children(); self.app.accounts.password_box.set_parent(None);
         self.app.accounts.imap_box.clear_children(); self.app.accounts.imap_box.set_parent(None);
         self.app.accounts.smtp_box.clear_children(); self.app.accounts.smtp_box.set_parent(None);
+        self.app.accounts.oauth_client_id_box.clear_children(); self.app.accounts.oauth_client_id_box.set_parent(None);
+        self.app.accounts.oauth_client_secret_box.clear_children(); self.app.accounts.oauth_client_secret_box.set_parent(None);
 
         self.app.services.list_box.scroll_box.clear_children(); self.app.services.list_box.scroll_box.set_parent(None);
 
@@ -685,10 +687,15 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
         use clear_ui::widget::focus::link_parent_child;
         match self.app.current_page {
             Page::Accounts => {
-                link_parent_child(&mut self.page_root_container, &mut self.app.accounts.email_box);
-                link_parent_child(&mut self.page_root_container, &mut self.app.accounts.password_box);
-                link_parent_child(&mut self.page_root_container, &mut self.app.accounts.imap_box);
-                link_parent_child(&mut self.page_root_container, &mut self.app.accounts.smtp_box);
+                if self.app.accounts.editing_oauth_creds {
+                    link_parent_child(&mut self.page_root_container, &mut self.app.accounts.oauth_client_id_box);
+                    link_parent_child(&mut self.page_root_container, &mut self.app.accounts.oauth_client_secret_box);
+                } else {
+                    link_parent_child(&mut self.page_root_container, &mut self.app.accounts.email_box);
+                    link_parent_child(&mut self.page_root_container, &mut self.app.accounts.password_box);
+                    link_parent_child(&mut self.page_root_container, &mut self.app.accounts.imap_box);
+                    link_parent_child(&mut self.page_root_container, &mut self.app.accounts.smtp_box);
+                }
             }
             Page::Typefaces => {
                 self.page_sec_containers.resize_with(3, clear_ui::widget::Container::new);
@@ -1525,10 +1532,15 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
             }
         }
         if self.app.current_page == Page::Accounts {
-            if self.app.accounts.email_box.cursor_moved(lx, ly) { changed = true; }
-            if self.app.accounts.password_box.cursor_moved(lx, ly) { changed = true; }
-            if self.app.accounts.imap_box.cursor_moved(lx, ly) { changed = true; }
-            if self.app.accounts.smtp_box.cursor_moved(lx, ly) { changed = true; }
+            if self.app.accounts.editing_oauth_creds {
+                if self.app.accounts.oauth_client_id_box.cursor_moved(lx, ly) { changed = true; }
+                if self.app.accounts.oauth_client_secret_box.cursor_moved(lx, ly) { changed = true; }
+            } else {
+                if self.app.accounts.email_box.cursor_moved(lx, ly) { changed = true; }
+                if self.app.accounts.password_box.cursor_moved(lx, ly) { changed = true; }
+                if self.app.accounts.imap_box.cursor_moved(lx, ly) { changed = true; }
+                if self.app.accounts.smtp_box.cursor_moved(lx, ly) { changed = true; }
+            }
         }
         if changed { self.needs_rebuild = true; }
         changed
@@ -1579,10 +1591,15 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
             match self.app.current_page {
                 Page::Accounts => {
                     let accs = &mut self.app.accounts;
-                    if accs.email_box.hit_test(lx, ly) { clicked_any_focusable = true; }
-                    if accs.password_box.hit_test(lx, ly) { clicked_any_focusable = true; }
-                    if accs.imap_box.hit_test(lx, ly) { clicked_any_focusable = true; }
-                    if accs.smtp_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if accs.editing_oauth_creds {
+                        if accs.oauth_client_id_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                        if accs.oauth_client_secret_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    } else {
+                        if accs.email_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                        if accs.password_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                        if accs.imap_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                        if accs.smtp_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    }
                 }
                 Page::Layout => {
                     for sb in &mut self.app.layout.spinboxes {
@@ -2038,47 +2055,61 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
             }
         }
         if self.app.current_page == Page::Accounts {
-            let tb = &mut self.app.accounts.email_box;
-            if state == clear_ui::widget::ElementState::Pressed && !tb.hit_test(lx, ly) { tb.unfocus(); }
-            if tb.mouse_input(button, state, lx, ly) {
-                self.needs_rebuild = true;
-            }
-            if state == clear_ui::widget::ElementState::Pressed && tb.take_change() {
-                let email_val = tb.text.trim().to_lowercase();
-                if email_val.ends_with("@gmail.com") {
-                    self.app.accounts.imap_box.text = "imap.gmail.com:993".to_string();
-                    self.app.accounts.imap_box.edit_buffer = "imap.gmail.com:993".to_string();
-                    self.app.accounts.smtp_box.text = "smtp.gmail.com:465".to_string();
-                    self.app.accounts.smtp_box.edit_buffer = "smtp.gmail.com:465".to_string();
-                } else if email_val.ends_with("@icloud.com") {
-                    self.app.accounts.imap_box.text = "imap.mail.me.com:993".to_string();
-                    self.app.accounts.imap_box.edit_buffer = "imap.mail.me.com:993".to_string();
-                    self.app.accounts.smtp_box.text = "smtp.mail.me.com:587".to_string();
-                    self.app.accounts.smtp_box.edit_buffer = "smtp.mail.me.com:587".to_string();
-                } else if email_val.ends_with("@outlook.com") || email_val.ends_with("@hotmail.com") {
-                    self.app.accounts.imap_box.text = "outlook.office365.com:993".to_string();
-                    self.app.accounts.imap_box.edit_buffer = "outlook.office365.com:993".to_string();
-                    self.app.accounts.smtp_box.text = "smtp.office365.com:587".to_string();
-                    self.app.accounts.smtp_box.edit_buffer = "smtp.office365.com:587".to_string();
-                }
-            }
-
-            let tb = &mut self.app.accounts.password_box;
-            if state == clear_ui::widget::ElementState::Pressed && !tb.hit_test(lx, ly) { tb.unfocus(); }
-            if tb.mouse_input(button, state, lx, ly) {
-                self.needs_rebuild = true;
-            }
+            if self.app.accounts.editing_oauth_creds {
+                let tb = &mut self.app.accounts.oauth_client_id_box;
+                if state == clear_ui::widget::ElementState::Pressed && !tb.hit_test(lx, ly) { tb.unfocus(); }
+                if tb.mouse_input(button, state, lx, ly) {
+                    self.needs_rebuild = true;
+                }
 
-            let tb = &mut self.app.accounts.imap_box;
-            if state == clear_ui::widget::ElementState::Pressed && !tb.hit_test(lx, ly) { tb.unfocus(); }
-            if tb.mouse_input(button, state, lx, ly) {
-                self.needs_rebuild = true;
-            }
+                let tb = &mut self.app.accounts.oauth_client_secret_box;
+                if state == clear_ui::widget::ElementState::Pressed && !tb.hit_test(lx, ly) { tb.unfocus(); }
+                if tb.mouse_input(button, state, lx, ly) {
+                    self.needs_rebuild = true;
+                }
+            } else {
+                let tb = &mut self.app.accounts.email_box;
+                if state == clear_ui::widget::ElementState::Pressed && !tb.hit_test(lx, ly) { tb.unfocus(); }
+                if tb.mouse_input(button, state, lx, ly) {
+                    self.needs_rebuild = true;
+                }
+                if state == clear_ui::widget::ElementState::Pressed && tb.take_change() {
+                    let email_val = tb.text.trim().to_lowercase();
+                    if email_val.ends_with("@gmail.com") {
+                        self.app.accounts.imap_box.text = "imap.gmail.com:993".to_string();
+                        self.app.accounts.imap_box.edit_buffer = "imap.gmail.com:993".to_string();
+                        self.app.accounts.smtp_box.text = "smtp.gmail.com:465".to_string();
+                        self.app.accounts.smtp_box.edit_buffer = "smtp.gmail.com:465".to_string();
+                    } else if email_val.ends_with("@icloud.com") {
+                        self.app.accounts.imap_box.text = "imap.mail.me.com:993".to_string();
+                        self.app.accounts.imap_box.edit_buffer = "imap.mail.me.com:993".to_string();
+                        self.app.accounts.smtp_box.text = "smtp.mail.me.com:587".to_string();
+                        self.app.accounts.smtp_box.edit_buffer = "smtp.mail.me.com:587".to_string();
+                    } else if email_val.ends_with("@outlook.com") || email_val.ends_with("@hotmail.com") {
+                        self.app.accounts.imap_box.text = "outlook.office365.com:993".to_string();
+                        self.app.accounts.imap_box.edit_buffer = "outlook.office365.com:993".to_string();
+                        self.app.accounts.smtp_box.text = "smtp.office365.com:587".to_string();
+                        self.app.accounts.smtp_box.edit_buffer = "smtp.office365.com:587".to_string();
+                    }
+                }
 
-            let tb = &mut self.app.accounts.smtp_box;
-            if state == clear_ui::widget::ElementState::Pressed && !tb.hit_test(lx, ly) { tb.unfocus(); }
-            if tb.mouse_input(button, state, lx, ly) {
-                self.needs_rebuild = true;
+                let tb = &mut self.app.accounts.password_box;
+                if state == clear_ui::widget::ElementState::Pressed && !tb.hit_test(lx, ly) { tb.unfocus(); }
+                if tb.mouse_input(button, state, lx, ly) {
+                    self.needs_rebuild = true;
+                }
+
+                let tb = &mut self.app.accounts.imap_box;
+                if state == clear_ui::widget::ElementState::Pressed && !tb.hit_test(lx, ly) { tb.unfocus(); }
+                if tb.mouse_input(button, state, lx, ly) {
+                    self.needs_rebuild = true;
+                }
+
+                let tb = &mut self.app.accounts.smtp_box;
+                if state == clear_ui::widget::ElementState::Pressed && !tb.hit_test(lx, ly) { tb.unfocus(); }
+                if tb.mouse_input(button, state, lx, ly) {
+                    self.needs_rebuild = true;
+                }
             }
         }
         if self.app.current_page == Page::Typefaces {
@@ -2634,33 +2665,40 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
         }
         if self.app.current_page == Page::Accounts {
             let mut consumed = false;
-            let tb = &mut self.app.accounts.email_box;
-            if tb.keyboard_input(event) {
-                consumed = true;
-                let email_val = tb.edit_buffer.trim().to_lowercase();
-                if email_val.ends_with("@gmail.com") {
-                    self.app.accounts.imap_box.text = "imap.gmail.com:993".to_string();
-                    self.app.accounts.imap_box.edit_buffer = "imap.gmail.com:993".to_string();
-                    self.app.accounts.smtp_box.text = "smtp.gmail.com:465".to_string();
-                    self.app.accounts.smtp_box.edit_buffer = "smtp.gmail.com:465".to_string();
-                } else if email_val.ends_with("@icloud.com") {
-                    self.app.accounts.imap_box.text = "imap.mail.me.com:993".to_string();
-                    self.app.accounts.imap_box.edit_buffer = "imap.mail.me.com:993".to_string();
-                    self.app.accounts.smtp_box.text = "smtp.mail.me.com:587".to_string();
-                    self.app.accounts.smtp_box.edit_buffer = "smtp.mail.me.com:587".to_string();
-                } else if email_val.ends_with("@outlook.com") || email_val.ends_with("@hotmail.com") {
-                    self.app.accounts.imap_box.text = "outlook.office365.com:993".to_string();
-                    self.app.accounts.imap_box.edit_buffer = "outlook.office365.com:993".to_string();
-                    self.app.accounts.smtp_box.text = "smtp.office365.com:587".to_string();
-                    self.app.accounts.smtp_box.edit_buffer = "smtp.office365.com:587".to_string();
-                }
-            }
-            let tb = &mut self.app.accounts.password_box;
-            if tb.keyboard_input(event) { consumed = true; }
-            let tb = &mut self.app.accounts.imap_box;
-            if tb.keyboard_input(event) { consumed = true; }
-            let tb = &mut self.app.accounts.smtp_box;
-            if tb.keyboard_input(event) { consumed = true; }
+            if self.app.accounts.editing_oauth_creds {
+                let tb = &mut self.app.accounts.oauth_client_id_box;
+                if tb.keyboard_input(event) { consumed = true; }
+                let tb = &mut self.app.accounts.oauth_client_secret_box;
+                if tb.keyboard_input(event) { consumed = true; }
+            } else {
+                let tb = &mut self.app.accounts.email_box;
+                if tb.keyboard_input(event) {
+                    consumed = true;
+                    let email_val = tb.edit_buffer.trim().to_lowercase();
+                    if email_val.ends_with("@gmail.com") {
+                        self.app.accounts.imap_box.text = "imap.gmail.com:993".to_string();
+                        self.app.accounts.imap_box.edit_buffer = "imap.gmail.com:993".to_string();
+                        self.app.accounts.smtp_box.text = "smtp.gmail.com:465".to_string();
+                        self.app.accounts.smtp_box.edit_buffer = "smtp.gmail.com:465".to_string();
+                    } else if email_val.ends_with("@icloud.com") {
+                        self.app.accounts.imap_box.text = "imap.mail.me.com:993".to_string();
+                        self.app.accounts.imap_box.edit_buffer = "imap.mail.me.com:993".to_string();
+                        self.app.accounts.smtp_box.text = "smtp.mail.me.com:587".to_string();
+                        self.app.accounts.smtp_box.edit_buffer = "smtp.mail.me.com:587".to_string();
+                    } else if email_val.ends_with("@outlook.com") || email_val.ends_with("@hotmail.com") {
+                        self.app.accounts.imap_box.text = "outlook.office365.com:993".to_string();
+                        self.app.accounts.imap_box.edit_buffer = "outlook.office365.com:993".to_string();
+                        self.app.accounts.smtp_box.text = "smtp.office365.com:587".to_string();
+                        self.app.accounts.smtp_box.edit_buffer = "smtp.office365.com:587".to_string();
+                    }
+                }
+                let tb = &mut self.app.accounts.password_box;
+                if tb.keyboard_input(event) { consumed = true; }
+                let tb = &mut self.app.accounts.imap_box;
+                if tb.keyboard_input(event) { consumed = true; }
+                let tb = &mut self.app.accounts.smtp_box;
+                if tb.keyboard_input(event) { consumed = true; }
+            }
             
             if consumed {
                 self.needs_rebuild = true;
diff --git a/src/pages/accounts.rs b/src/pages/accounts.rs
index d8fdebe..3440982 100644
--- a/src/pages/accounts.rs
+++ b/src/pages/accounts.rs
@@ -36,6 +36,9 @@ pub struct AccountsState {
     pub status_msg: Option<String>,
     pub status_msg_timer: f32,
     pub oauth_listener_running: bool,
+    pub editing_oauth_creds: bool,
+    pub oauth_client_id_box: TextBox,
+    pub oauth_client_secret_box: TextBox,
 }
 
 impl AccountsState {
@@ -49,6 +52,14 @@ impl AccountsState {
         };
         state.imap_box = TextBox::new(String::new()).with_multiline(false).with_draw_bg_border(true).with_label("IMAP Server");
         state.smtp_box = TextBox::new(String::new()).with_multiline(false).with_draw_bg_border(true).with_label("SMTP Server");
+        
+        let client_config = load_google_client_config();
+        state.oauth_client_id_box = TextBox::new(client_config.client_id).with_multiline(false).with_draw_bg_border(true).with_label("Google Client ID");
+        state.oauth_client_secret_box = {
+            let mut tb = TextBox::new(client_config.client_secret).with_multiline(false).with_draw_bg_border(true).with_label("Google Client Secret");
+            tb.is_password = true;
+            tb
+        };
         state
     }
 }
@@ -66,6 +77,9 @@ pub enum AccountsMessage {
     GoogleLoginInit,
     GoogleLoginSuccess(AccountInfo),
     ICloudLoginHelp,
+    EditOAuthCredsStart,
+    EditOAuthCredsSave,
+    EditOAuthCredsCancel,
 }
 
 pub fn get_accounts_path() -> std::path::PathBuf {
@@ -320,7 +334,7 @@ pub fn view(state: &mut AccountsState, cx: f32, cy: f32, cw: f32, _ch: f32) -> P
                 } else {
                     acc.email.clone()
                 };
-                let is_selected = state.selected_idx == Some(idx) && !state.adding_new;
+                let is_selected = state.selected_idx == Some(idx) && !state.adding_new && !state.editing_oauth_creds;
                 let bg_col = if is_selected { [0.20, 0.40, 0.65, 0.4] } else { [0.10, 0.10, 0.16, 0.3] };
                 pc.button(
                     &label,
@@ -353,8 +367,22 @@ pub fn view(state: &mut AccountsState, cx: f32, cy: f32, cw: f32, _ch: f32) -> P
         );
         left_y += row_h + row_gap;
 
+        let oauth_bg = if state.editing_oauth_creds { [0.20, 0.40, 0.65, 0.4] } else { [0.15, 0.15, 0.20, 1.0] };
+        pc.button(
+            "Google API Settings",
+            left_x,
+            left_y,
+            left_w,
+            row_h,
+            oauth_bg,
+            [0.25, 0.25, 0.30, 1.0],
+            [1.0, 1.0, 1.0, 1.0],
+            AppAction::Accounts(AccountsMessage::EditOAuthCredsStart),
+        );
+        left_y += row_h + row_gap;
+
         if let Some(selected_idx) = state.selected_idx {
-            if selected_idx < state.accounts.len() && !state.adding_new {
+            if selected_idx < state.accounts.len() && !state.adding_new && !state.editing_oauth_creds {
                 let acc = &state.accounts[selected_idx];
                 if !acc.is_default {
                     pc.button(
@@ -469,6 +497,54 @@ pub fn view(state: &mut AccountsState, cx: f32, cy: f32, cw: f32, _ch: f32) -> P
                 AppAction::Accounts(AccountsMessage::AddAccountCancel),
             );
             right_y += row_h + 12.0;
+        } else if state.editing_oauth_creds {
+            pc.text("Google OAuth Credentials", right_x, right_y, 14.0, [0.35, 0.65, 0.90, 1.0]);
+            right_y += 24.0;
+
+            pc.text("Configures client ID & secret from your Google Cloud Console.", right_x, right_y, 11.0, TEXT_DIM);
+            right_y += 18.0;
+            pc.text("Required: Gmail API enabled & redirect URI set to http://127.0.0.1:8080", right_x, right_y, 11.0, TEXT_DIM);
+            right_y += 18.0;
+
+            let widget_h = 26.0;
+            let field_gap = 14.0;
+
+            // Client ID textbox
+            let client_id_top = state.oauth_client_id_box.top_room();
+            state.oauth_client_id_box.set_row_rect(right_x, right_w);
+            clear_ui::layout::render_widget(&mut pc, &mut state.oauth_client_id_box, right_x, right_y + client_id_top, right_w, widget_h);
+            right_y += widget_h + client_id_top + field_gap;
+
+            // Client Secret textbox
+            let client_secret_top = state.oauth_client_secret_box.top_room();
+            state.oauth_client_secret_box.set_row_rect(right_x, right_w);
+            clear_ui::layout::render_widget(&mut pc, &mut state.oauth_client_secret_box, right_x, right_y + client_secret_top, right_w, widget_h);
+            right_y += widget_h + client_secret_top + field_gap;
+
+            let helper_w = (right_w - 8.0) / 2.0;
+            pc.button(
+                "Save Credentials",
+                right_x,
+                right_y,
+                helper_w,
+                row_h,
+                [0.13, 0.18, 0.14, 1.0],
+                [0.25, 0.30, 0.26, 1.0],
+                [1.0, 1.0, 1.0, 1.0],
+                AppAction::Accounts(AccountsMessage::EditOAuthCredsSave),
+            );
+            pc.button(
+                "Cancel",
+                right_x + helper_w + 8.0,
+                right_y,
+                helper_w,
+                row_h,
+                [0.33, 0.20, 0.20, 1.0],
+                [0.45, 0.25, 0.25, 1.0],
+                [1.0, 1.0, 1.0, 1.0],
+                AppAction::Accounts(AccountsMessage::EditOAuthCredsCancel),
+            );
+            right_y += row_h + 12.0;
         } else if let Some(selected_idx) = state.selected_idx {
             if selected_idx < state.accounts.len() {
                 let acc = &state.accounts[selected_idx];
@@ -537,9 +613,11 @@ pub fn update(state: &mut AccountsState, msg: AccountsMessage) {
         AccountsMessage::SelectAccount(idx) => {
             state.selected_idx = Some(idx);
             state.adding_new = false;
+            state.editing_oauth_creds = false;
         }
         AccountsMessage::AddAccountStart => {
             state.adding_new = true;
+            state.editing_oauth_creds = false;
             state.email_box.text = String::new();
             state.email_box.edit_buffer = String::new();
             state.password_box.text = String::new();
@@ -631,5 +709,47 @@ pub fn update(state: &mut AccountsState, msg: AccountsMessage) {
             let _ = std::process::Command::new("xdg-open").arg("https://appleid.apple.com/").spawn();
             state.status_msg = Some("Generate iCloud App Password...".to_string());
         }
+        AccountsMessage::EditOAuthCredsStart => {
+            state.editing_oauth_creds = true;
+            state.adding_new = false;
+            let config = load_google_client_config();
+            state.oauth_client_id_box.text = config.client_id.clone();
+            state.oauth_client_id_box.edit_buffer = config.client_id;
+            state.oauth_client_secret_box.text = config.client_secret.clone();
+            state.oauth_client_secret_box.edit_buffer = config.client_secret;
+        }
+        AccountsMessage::EditOAuthCredsSave => {
+            let client_id = state.oauth_client_id_box.text.trim().to_string();
+            let client_secret = state.oauth_client_secret_box.text.trim().to_string();
+            if client_id.is_empty() || client_secret.is_empty() {
+                state.status_msg = Some("Both Client ID and Client Secret are required!".to_string());
+                return;
+            }
+            let config = GoogleClientConfig {
+                client_id,
+                client_secret,
+            };
+            let p = std::path::PathBuf::from("/home/lsgalante/.config/ccec/google_client.json");
+            if let Some(parent) = p.parent() {
+                let _ = std::fs::create_dir_all(parent);
+            }
+            if let Ok(content) = serde_json::to_string_pretty(&config) {
+                let _ = std::fs::write(&p, content);
+                #[cfg(unix)]
+                {
+                    use std::os::unix::fs::PermissionsExt;
+                    if let Ok(metadata) = std::fs::metadata(&p) {
+                        let mut perms = metadata.permissions();
+                        perms.set_mode(0o600);
+                        let _ = std::fs::set_permissions(&p, perms);
+                    }
+                }
+            }
+            state.editing_oauth_creds = false;
+            state.status_msg = Some("Google API credentials updated successfully!".to_string());
+        }
+        AccountsMessage::EditOAuthCredsCancel => {
+            state.editing_oauth_creds = false;
+        }
     }
 }