system settings
git clone https://git.lucas.co/cce-system-interface.git
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;
+ }
}
}