Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src-tauri/src/autostart.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use tauri::AppHandle;
use tauri_plugin_autostart::ManagerExt;

#[tauri::command]
pub fn is_autostart_enabled_cmd(app: AppHandle) -> Result<bool, String> {
Ok(app.autolaunch().is_enabled().unwrap_or(false))
}

#[tauri::command]
pub fn set_autostart_cmd(app: AppHandle, enabled: bool) -> Result<(), String> {
let result = if enabled {
app.autolaunch().enable()
} else {
app.autolaunch().disable()
};
result.map_err(|e| format!("Failed to set autostart: {}", e))
}
30 changes: 4 additions & 26 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod avatars;
mod auth;
mod autostart;
mod github;
mod menu;
mod models;
Expand All @@ -11,7 +12,6 @@ mod updater;

use tauri::tray::TrayIconBuilder;
use tauri::{Emitter, Manager};
use tauri_plugin_autostart::ManagerExt;

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
Expand Down Expand Up @@ -44,7 +44,9 @@ pub fn run() {
settings::get_settings_cmd,
settings::save_settings_cmd,
updater::check_for_update_cmd,
updater::install_update_cmd
updater::install_update_cmd,
autostart::is_autostart_enabled_cmd,
autostart::set_autostart_cmd
])
.setup(|app| {
// Restore saved auth token from disk (if any)
Expand Down Expand Up @@ -202,30 +204,6 @@ fn handle_menu_event(app: &tauri::AppHandle, id: &str) {
});
}

"autostart_toggle" => {
let currently_enabled = app.autolaunch().is_enabled().unwrap_or(false);
let toggle_result = if currently_enabled {
app.autolaunch().disable()
} else {
app.autolaunch().enable()
};
if let Err(e) = toggle_result {
eprintln!("[autostart] Failed to toggle autostart: {}", e);
}

let prs = {
let state = app.state::<state::AppState>();
let val = state.prs.lock().unwrap().clone();
val
};
let state = app.state::<state::AppState>();
let tray_guard = state.tray.lock().unwrap();
if let Some(tray) = tray_guard.as_ref() {
if let Ok(new_menu) = menu::build_pr_menu(app, &prs, &state.avatar_cache) {
let _ = tray.set_menu(Some(new_menu));
}
}
}

"sign_in" => {
let app = app.clone();
Expand Down
20 changes: 4 additions & 16 deletions src-tauri/src/menu.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use tauri::AppHandle;
use tauri::menu::{CheckMenuItem, IconMenuItem, Menu, MenuItem, PredefinedMenuItem};
use tauri_plugin_autostart::ManagerExt;
use tauri::menu::{IconMenuItem, Menu, MenuItem, PredefinedMenuItem};

use crate::avatars::AvatarCache;
use crate::models::{CheckStatus, PrState, PullRequest};
Expand Down Expand Up @@ -192,24 +191,13 @@ pub fn build_pr_menu(
menu.append(&see_all)?;
let refresh = MenuItem::with_id(app, "refresh", "Refresh", true, None::<&str>)?;
menu.append(&refresh)?;
let sep2 = PredefinedMenuItem::separator(app)?;
menu.append(&sep2)?;
let check_updates =
MenuItem::with_id(app, "check_updates", "Check for Updates", true, None::<&str>)?;
menu.append(&check_updates)?;
let autostart_enabled = app.autolaunch().is_enabled().unwrap_or(false);
let autostart_toggle = CheckMenuItem::with_id(
app,
"autostart_toggle",
"Launch at login",
true,
autostart_enabled,
None::<&str>,
)?;
menu.append(&autostart_toggle)?;
let settings =
MenuItem::with_id(app, "settings", "Settings...", true, None::<&str>)?;
let settings = MenuItem::with_id(app, "settings", "Settings", true, None::<&str>)?;
menu.append(&settings)?;
let sep2 = PredefinedMenuItem::separator(app)?;
menu.append(&sep2)?;
let logout = MenuItem::with_id(app, "logout", "Logout", true, None::<&str>)?;
menu.append(&logout)?;
let quit = MenuItem::with_id(app, "quit", "Quit PR Buddy", true, None::<&str>)?;
Expand Down
57 changes: 57 additions & 0 deletions src/lib/SettingsPage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,34 @@
let loaded = $state(false);
let currentTheme = $state<ThemePreference>("system");
let setThemePreference: (t: ThemePreference) => void = () => {};
let launchAtLoginEnabled = $state(false);
let autoStartLoaded = $state(false);
let autoStartToggleCount = 0; // generation counter to discard stale reads/rollbacks

// Serialize saves so rapid toggles don't race each other
let saveChain = Promise.resolve();
let autoStartChain = Promise.resolve();

onMount(() => {
void loadSettings();
void loadAutostartSetting();
void initTheme();
});

async function loadAutostartSetting() {
const gen = autoStartToggleCount;
try {
const enabled = await invoke<boolean>("is_autostart_enabled_cmd");
// Only apply if user hasn't toggled while we were loading
if (autoStartToggleCount === gen) {
launchAtLoginEnabled = Boolean(enabled);
}
} catch (e) {
console.error("[settings] Failed to load autostart setting:", e);
} finally {
autoStartLoaded = true;
}
}

async function initTheme() {
if (typeof window !== "undefined" && typeof window.matchMedia !== "function") {
Expand Down Expand Up @@ -92,6 +111,21 @@
void save();
}

function toggleLaunchAtLogin() {
const enabled = !launchAtLoginEnabled;
launchAtLoginEnabled = enabled;
const gen = ++autoStartToggleCount;
autoStartChain = autoStartChain
.then(async () => { await invoke("set_autostart_cmd", { enabled }); })
.catch((e: unknown) => {
console.error("[settings] Failed to set autostart setting:", e);
// Only rollback if no newer toggle has happened since
if (autoStartToggleCount === gen) {
launchAtLoginEnabled = !enabled;
}
});
}

function toggleRepo(repo: string) {
const idx = settings.hidden_repos.indexOf(repo);
if (idx >= 0) {
Expand Down Expand Up @@ -143,6 +177,29 @@
<div class="w-5 h-5 border-2 border-accent border-t-transparent rounded-full animate-spin"></div>
</div>
{:else}
<!-- General -->
<section>
<h2 class="text-xs font-semibold text-content-secondary uppercase tracking-wide mb-2">General</h2>
<button
onclick={toggleLaunchAtLogin}
disabled={!autoStartLoaded}
class="flex items-center justify-between w-full px-3 py-2 rounded-lg
hover:bg-surface-hover transition-colors text-left
{autoStartLoaded ? '' : 'opacity-50 cursor-not-allowed'}"
>
<span class="text-sm text-content">Launch at Login</span>
<div
class="w-8 h-[18px] rounded-full transition-colors relative
{launchAtLoginEnabled ? 'bg-accent' : 'bg-surface-secondary'}"
>
<div
class="absolute top-[2px] w-[14px] h-[14px] rounded-full bg-white transition-transform
{launchAtLoginEnabled ? 'translate-x-[16px]' : 'translate-x-[2px]'}"
></div>
</div>
</button>
</section>

<!-- Theme -->
<section>
<h2 class="text-xs font-semibold text-content-secondary uppercase tracking-wide mb-2">Theme</h2>
Expand Down
Loading