Switch to disable update checking (#362)
* codex thinks it did it * run format * properly disable on startup * text
This commit is contained in:
parent
ee571b6810
commit
39adf1b455
13 changed files with 187 additions and 96 deletions
|
|
@ -165,8 +165,11 @@ fn initialize_core_logic(app_handle: &AppHandle) {
|
||||||
show_main_window(app);
|
show_main_window(app);
|
||||||
}
|
}
|
||||||
"check_updates" => {
|
"check_updates" => {
|
||||||
show_main_window(app);
|
let settings = settings::get_settings(app);
|
||||||
let _ = app.emit("check-for-updates", ());
|
if settings.update_checks_enabled {
|
||||||
|
show_main_window(app);
|
||||||
|
let _ = app.emit("check-for-updates", ());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"cancel" => {
|
"cancel" => {
|
||||||
use crate::utils::cancel_current_operation;
|
use crate::utils::cancel_current_operation;
|
||||||
|
|
@ -205,6 +208,10 @@ fn initialize_core_logic(app_handle: &AppHandle) {
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
fn trigger_update_check(app: AppHandle) -> Result<(), String> {
|
fn trigger_update_check(app: AppHandle) -> Result<(), String> {
|
||||||
|
let settings = settings::get_settings(&app);
|
||||||
|
if !settings.update_checks_enabled {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
app.emit("check-for-updates", ())
|
app.emit("check-for-updates", ())
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -246,6 +253,7 @@ pub fn run() {
|
||||||
shortcut::suspend_binding,
|
shortcut::suspend_binding,
|
||||||
shortcut::resume_binding,
|
shortcut::resume_binding,
|
||||||
shortcut::change_mute_while_recording_setting,
|
shortcut::change_mute_while_recording_setting,
|
||||||
|
shortcut::change_update_checks_setting,
|
||||||
trigger_update_check,
|
trigger_update_check,
|
||||||
commands::cancel_operation,
|
commands::cancel_operation,
|
||||||
commands::get_app_dir_path,
|
commands::get_app_dir_path,
|
||||||
|
|
@ -300,30 +308,29 @@ pub fn run() {
|
||||||
)
|
)
|
||||||
.expect("Failed to export typescript bindings");
|
.expect("Failed to export typescript bindings");
|
||||||
|
|
||||||
let mut builder = tauri::Builder::default()
|
let mut builder = tauri::Builder::default().plugin(
|
||||||
.plugin(
|
LogBuilder::new()
|
||||||
LogBuilder::new()
|
.level(log::LevelFilter::Trace) // Set to most verbose level globally
|
||||||
.level(log::LevelFilter::Trace) // Set to most verbose level globally
|
.max_file_size(500_000)
|
||||||
.max_file_size(500_000)
|
.rotation_strategy(RotationStrategy::KeepOne)
|
||||||
.rotation_strategy(RotationStrategy::KeepOne)
|
.clear_targets()
|
||||||
.clear_targets()
|
.targets([
|
||||||
.targets([
|
// Console output respects RUST_LOG environment variable
|
||||||
// Console output respects RUST_LOG environment variable
|
Target::new(TargetKind::Stdout).filter({
|
||||||
Target::new(TargetKind::Stdout).filter({
|
let console_filter = console_filter.clone();
|
||||||
let console_filter = console_filter.clone();
|
move |metadata| console_filter.enabled(metadata)
|
||||||
move |metadata| console_filter.enabled(metadata)
|
}),
|
||||||
}),
|
// File logs respect the user's settings (stored in FILE_LOG_LEVEL atomic)
|
||||||
// File logs respect the user's settings (stored in FILE_LOG_LEVEL atomic)
|
Target::new(TargetKind::LogDir {
|
||||||
Target::new(TargetKind::LogDir {
|
file_name: Some("handy".into()),
|
||||||
file_name: Some("handy".into()),
|
})
|
||||||
})
|
.filter(|metadata| {
|
||||||
.filter(|metadata| {
|
let file_level = FILE_LOG_LEVEL.load(Ordering::Relaxed);
|
||||||
let file_level = FILE_LOG_LEVEL.load(Ordering::Relaxed);
|
metadata.level() <= level_filter_from_u8(file_level)
|
||||||
metadata.level() <= level_filter_from_u8(file_level)
|
}),
|
||||||
}),
|
])
|
||||||
])
|
.build(),
|
||||||
.build(),
|
);
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ const OVERLAY_BOTTOM_OFFSET: f64 = 40.0;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn force_overlay_topmost(overlay_window: &tauri::webview::WebviewWindow) {
|
fn force_overlay_topmost(overlay_window: &tauri::webview::WebviewWindow) {
|
||||||
use windows::Win32::UI::WindowsAndMessaging::{
|
use windows::Win32::UI::WindowsAndMessaging::{
|
||||||
SetWindowPos, HWND_TOPMOST, SWP_NOMOVE, SWP_NOSIZE, SWP_NOACTIVATE, SWP_SHOWWINDOW,
|
SetWindowPos, HWND_TOPMOST, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_SHOWWINDOW,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clone because run_on_main_thread takes 'static
|
// Clone because run_on_main_thread takes 'static
|
||||||
|
|
@ -58,7 +58,10 @@ fn force_overlay_topmost(overlay_window: &tauri::webview::WebviewWindow) {
|
||||||
let _ = SetWindowPos(
|
let _ = SetWindowPos(
|
||||||
hwnd,
|
hwnd,
|
||||||
Some(HWND_TOPMOST),
|
Some(HWND_TOPMOST),
|
||||||
0, 0, 0, 0,
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW,
|
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -173,32 +176,26 @@ pub fn create_recording_overlay(app_handle: &AppHandle) {
|
||||||
if let Some((x, y)) = calculate_overlay_position(app_handle) {
|
if let Some((x, y)) = calculate_overlay_position(app_handle) {
|
||||||
// PanelBuilder creates a Tauri window then converts it to NSPanel.
|
// PanelBuilder creates a Tauri window then converts it to NSPanel.
|
||||||
// The window remains registered, so get_webview_window() still works.
|
// The window remains registered, so get_webview_window() still works.
|
||||||
match PanelBuilder::<_, RecordingOverlayPanel>::new(
|
match PanelBuilder::<_, RecordingOverlayPanel>::new(app_handle, "recording_overlay")
|
||||||
app_handle,
|
.url(WebviewUrl::App("src/overlay/index.html".into()))
|
||||||
"recording_overlay"
|
.title("Recording")
|
||||||
)
|
.position(tauri::Position::Logical(tauri::LogicalPosition { x, y }))
|
||||||
.url(WebviewUrl::App("src/overlay/index.html".into()))
|
.level(PanelLevel::Status)
|
||||||
.title("Recording")
|
.size(tauri::Size::Logical(tauri::LogicalSize {
|
||||||
.position(tauri::Position::Logical(tauri::LogicalPosition { x, y }))
|
width: OVERLAY_WIDTH,
|
||||||
.level(PanelLevel::Status)
|
height: OVERLAY_HEIGHT,
|
||||||
.size(tauri::Size::Logical(tauri::LogicalSize {
|
}))
|
||||||
width: OVERLAY_WIDTH,
|
.has_shadow(false)
|
||||||
height: OVERLAY_HEIGHT
|
.transparent(true)
|
||||||
}))
|
.no_activate(true)
|
||||||
.has_shadow(false)
|
.corner_radius(0.0)
|
||||||
.transparent(true)
|
.with_window(|w| w.decorations(false).transparent(true))
|
||||||
.no_activate(true)
|
.collection_behavior(
|
||||||
.corner_radius(0.0)
|
CollectionBehavior::new()
|
||||||
.with_window(|w| {
|
.can_join_all_spaces()
|
||||||
w.decorations(false)
|
.full_screen_auxiliary(),
|
||||||
.transparent(true)
|
)
|
||||||
})
|
.build()
|
||||||
.collection_behavior(
|
|
||||||
CollectionBehavior::new()
|
|
||||||
.can_join_all_spaces()
|
|
||||||
.full_screen_auxiliary()
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
{
|
{
|
||||||
Ok(panel) => {
|
Ok(panel) => {
|
||||||
let _ = panel.hide();
|
let _ = panel.hide();
|
||||||
|
|
@ -221,7 +218,8 @@ pub fn show_recording_overlay(app_handle: &AppHandle) {
|
||||||
if let Some(overlay_window) = app_handle.get_webview_window("recording_overlay") {
|
if let Some(overlay_window) = app_handle.get_webview_window("recording_overlay") {
|
||||||
// Update position before showing to prevent flicker from position changes
|
// Update position before showing to prevent flicker from position changes
|
||||||
if let Some((x, y)) = calculate_overlay_position(app_handle) {
|
if let Some((x, y)) = calculate_overlay_position(app_handle) {
|
||||||
let _ = overlay_window.set_position(tauri::Position::Logical(tauri::LogicalPosition { x, y }));
|
let _ = overlay_window
|
||||||
|
.set_position(tauri::Position::Logical(tauri::LogicalPosition { x, y }));
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = overlay_window.show();
|
let _ = overlay_window.show();
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,8 @@ pub struct AppSettings {
|
||||||
pub start_hidden: bool,
|
pub start_hidden: bool,
|
||||||
#[serde(default = "default_autostart_enabled")]
|
#[serde(default = "default_autostart_enabled")]
|
||||||
pub autostart_enabled: bool,
|
pub autostart_enabled: bool,
|
||||||
|
#[serde(default = "default_update_checks_enabled")]
|
||||||
|
pub update_checks_enabled: bool,
|
||||||
#[serde(default = "default_model")]
|
#[serde(default = "default_model")]
|
||||||
pub selected_model: String,
|
pub selected_model: String,
|
||||||
#[serde(default = "default_always_on_microphone")]
|
#[serde(default = "default_always_on_microphone")]
|
||||||
|
|
@ -305,6 +307,10 @@ fn default_autostart_enabled() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_update_checks_enabled() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn default_selected_language() -> String {
|
fn default_selected_language() -> String {
|
||||||
"auto".to_string()
|
"auto".to_string()
|
||||||
}
|
}
|
||||||
|
|
@ -451,6 +457,7 @@ pub fn get_default_settings() -> AppSettings {
|
||||||
sound_theme: default_sound_theme(),
|
sound_theme: default_sound_theme(),
|
||||||
start_hidden: default_start_hidden(),
|
start_hidden: default_start_hidden(),
|
||||||
autostart_enabled: default_autostart_enabled(),
|
autostart_enabled: default_autostart_enabled(),
|
||||||
|
update_checks_enabled: default_update_checks_enabled(),
|
||||||
selected_model: "".to_string(),
|
selected_model: "".to_string(),
|
||||||
always_on_microphone: false,
|
always_on_microphone: false,
|
||||||
selected_microphone: None,
|
selected_microphone: None,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ pub fn init_shortcuts(app: &AppHandle) {
|
||||||
let default_bindings = settings::get_default_settings().bindings;
|
let default_bindings = settings::get_default_settings().bindings;
|
||||||
let user_settings = settings::load_or_create_app_settings(app);
|
let user_settings = settings::load_or_create_app_settings(app);
|
||||||
|
|
||||||
|
|
||||||
// Register all default shortcuts, applying user customizations
|
// Register all default shortcuts, applying user customizations
|
||||||
for (id, default_binding) in default_bindings {
|
for (id, default_binding) in default_bindings {
|
||||||
if id == "cancel" {
|
if id == "cancel" {
|
||||||
|
|
@ -284,6 +283,24 @@ pub fn change_autostart_setting(app: AppHandle, enabled: bool) -> Result<(), Str
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub fn change_update_checks_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
||||||
|
let mut settings = settings::get_settings(&app);
|
||||||
|
settings.update_checks_enabled = enabled;
|
||||||
|
settings::write_settings(&app, settings);
|
||||||
|
|
||||||
|
let _ = app.emit(
|
||||||
|
"settings-changed",
|
||||||
|
serde_json::json!({
|
||||||
|
"setting": "update_checks_enabled",
|
||||||
|
"value": enabled
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub fn update_custom_words(app: AppHandle, words: Vec<String>) -> Result<(), String> {
|
pub fn update_custom_words(app: AppHandle, words: Vec<String>) -> Result<(), String> {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::settings;
|
||||||
use tauri::image::Image;
|
use tauri::image::Image;
|
||||||
use tauri::menu::{Menu, MenuItem, PredefinedMenuItem};
|
use tauri::menu::{Menu, MenuItem, PredefinedMenuItem};
|
||||||
use tauri::tray::TrayIcon;
|
use tauri::tray::TrayIcon;
|
||||||
|
|
@ -74,6 +75,8 @@ pub fn change_tray_icon(app: &AppHandle, icon: TrayIconState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_tray_menu(app: &AppHandle, state: &TrayIconState) {
|
pub fn update_tray_menu(app: &AppHandle, state: &TrayIconState) {
|
||||||
|
let settings = settings::get_settings(app);
|
||||||
|
|
||||||
// Platform-specific accelerators
|
// Platform-specific accelerators
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let (settings_accelerator, quit_accelerator) = (Some("Cmd+,"), Some("Cmd+Q"));
|
let (settings_accelerator, quit_accelerator) = (Some("Cmd+,"), Some("Cmd+Q"));
|
||||||
|
|
@ -90,7 +93,7 @@ pub fn update_tray_menu(app: &AppHandle, state: &TrayIconState) {
|
||||||
app,
|
app,
|
||||||
"check_updates",
|
"check_updates",
|
||||||
"Check for Updates...",
|
"Check for Updates...",
|
||||||
true,
|
settings.update_checks_enabled,
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
)
|
)
|
||||||
.expect("failed to create check updates item");
|
.expect("failed to create check updates item");
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,14 @@ async changeMuteWhileRecordingSetting(enabled: boolean) : Promise<Result<null, s
|
||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async changeUpdateChecksSetting(enabled: boolean) : Promise<Result<null, string>> {
|
||||||
|
try {
|
||||||
|
return { status: "ok", data: await TAURI_INVOKE("change_update_checks_setting", { enabled }) };
|
||||||
|
} catch (e) {
|
||||||
|
if(e instanceof Error) throw e;
|
||||||
|
else return { status: "error", error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
async triggerUpdateCheck() : Promise<Result<null, string>> {
|
async triggerUpdateCheck() : Promise<Result<null, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("trigger_update_check") };
|
return { status: "ok", data: await TAURI_INVOKE("trigger_update_check") };
|
||||||
|
|
@ -594,7 +602,7 @@ async isLaptop() : Promise<Result<boolean, string>> {
|
||||||
|
|
||||||
/** user-defined types **/
|
/** user-defined types **/
|
||||||
|
|
||||||
export type AppSettings = { bindings: Partial<{ [key in string]: ShortcutBinding }>; push_to_talk: boolean; audio_feedback: boolean; audio_feedback_volume?: number; sound_theme?: SoundTheme; start_hidden?: boolean; autostart_enabled?: boolean; selected_model?: string; always_on_microphone?: boolean; selected_microphone?: string | null; clamshell_microphone?: string | null; selected_output_device?: string | null; translate_to_english?: boolean; selected_language?: string; overlay_position?: OverlayPosition; debug_mode?: boolean; log_level?: LogLevel; custom_words?: string[]; model_unload_timeout?: ModelUnloadTimeout; word_correction_threshold?: number; history_limit?: string; recording_retention_period?: RecordingRetentionPeriod; paste_method?: PasteMethod; clipboard_handling?: ClipboardHandling; post_process_enabled?: boolean; post_process_provider_id?: string; post_process_providers?: PostProcessProvider[]; post_process_api_keys?: Partial<{ [key in string]: string }>; post_process_models?: Partial<{ [key in string]: string }>; post_process_prompts?: LLMPrompt[]; post_process_selected_prompt_id?: string | null; mute_while_recording?: boolean }
|
export type AppSettings = { bindings: Partial<{ [key in string]: ShortcutBinding }>; push_to_talk: boolean; audio_feedback: boolean; audio_feedback_volume?: number; sound_theme?: SoundTheme; start_hidden?: boolean; autostart_enabled?: boolean; update_checks_enabled?: boolean; selected_model?: string; always_on_microphone?: boolean; selected_microphone?: string | null; clamshell_microphone?: string | null; selected_output_device?: string | null; translate_to_english?: boolean; selected_language?: string; overlay_position?: OverlayPosition; debug_mode?: boolean; log_level?: LogLevel; custom_words?: string[]; model_unload_timeout?: ModelUnloadTimeout; word_correction_threshold?: number; history_limit?: string; recording_retention_period?: RecordingRetentionPeriod; paste_method?: PasteMethod; clipboard_handling?: ClipboardHandling; post_process_enabled?: boolean; post_process_provider_id?: string; post_process_providers?: PostProcessProvider[]; post_process_api_keys?: Partial<{ [key in string]: string }>; post_process_models?: Partial<{ [key in string]: string }>; post_process_prompts?: LLMPrompt[]; post_process_selected_prompt_id?: string | null; mute_while_recording?: boolean }
|
||||||
export type AudioDevice = { index: string; name: string; is_default: boolean }
|
export type AudioDevice = { index: string; name: string; is_default: boolean }
|
||||||
export type BindingResponse = { success: boolean; binding: ShortcutBinding | null; error: string | null }
|
export type BindingResponse = { success: boolean; binding: ShortcutBinding | null; error: string | null }
|
||||||
export type ClipboardHandling = "dont_modify" | "copy_to_clipboard"
|
export type ClipboardHandling = "dont_modify" | "copy_to_clipboard"
|
||||||
|
|
|
||||||
|
|
@ -84,17 +84,15 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
|
||||||
if (editingShortcutId && originalBinding) {
|
if (editingShortcutId && originalBinding) {
|
||||||
try {
|
try {
|
||||||
await updateBinding(editingShortcutId, originalBinding);
|
await updateBinding(editingShortcutId, originalBinding);
|
||||||
await commands.resumeBinding(editingShortcutId).catch(
|
await commands
|
||||||
console.error,
|
.resumeBinding(editingShortcutId)
|
||||||
);
|
.catch(console.error);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to restore original binding:", error);
|
console.error("Failed to restore original binding:", error);
|
||||||
toast.error("Failed to restore original shortcut");
|
toast.error("Failed to restore original shortcut");
|
||||||
}
|
}
|
||||||
} else if (editingShortcutId) {
|
} else if (editingShortcutId) {
|
||||||
await commands.resumeBinding(editingShortcutId).catch(
|
await commands.resumeBinding(editingShortcutId).catch(console.error);
|
||||||
console.error,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
setEditingShortcutId(null);
|
setEditingShortcutId(null);
|
||||||
setKeyPressed([]);
|
setKeyPressed([]);
|
||||||
|
|
@ -138,9 +136,9 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
|
||||||
try {
|
try {
|
||||||
await updateBinding(editingShortcutId, newShortcut);
|
await updateBinding(editingShortcutId, newShortcut);
|
||||||
// Re-register the shortcut now that recording is finished
|
// Re-register the shortcut now that recording is finished
|
||||||
await commands.resumeBinding(editingShortcutId).catch(
|
await commands
|
||||||
console.error,
|
.resumeBinding(editingShortcutId)
|
||||||
);
|
.catch(console.error);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to change binding:", error);
|
console.error("Failed to change binding:", error);
|
||||||
toast.error(`Failed to set shortcut: ${error}`);
|
toast.error(`Failed to set shortcut: ${error}`);
|
||||||
|
|
@ -149,9 +147,9 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
|
||||||
if (originalBinding) {
|
if (originalBinding) {
|
||||||
try {
|
try {
|
||||||
await updateBinding(editingShortcutId, originalBinding);
|
await updateBinding(editingShortcutId, originalBinding);
|
||||||
await commands.resumeBinding(editingShortcutId).catch(
|
await commands
|
||||||
console.error,
|
.resumeBinding(editingShortcutId)
|
||||||
);
|
.catch(console.error);
|
||||||
} catch (resetError) {
|
} catch (resetError) {
|
||||||
console.error("Failed to reset binding:", resetError);
|
console.error("Failed to reset binding:", resetError);
|
||||||
toast.error("Failed to reset shortcut to original value");
|
toast.error("Failed to reset shortcut to original value");
|
||||||
|
|
@ -177,17 +175,15 @@ export const HandyShortcut: React.FC<HandyShortcutProps> = ({
|
||||||
if (editingShortcutId && originalBinding) {
|
if (editingShortcutId && originalBinding) {
|
||||||
try {
|
try {
|
||||||
await updateBinding(editingShortcutId, originalBinding);
|
await updateBinding(editingShortcutId, originalBinding);
|
||||||
await commands.resumeBinding(editingShortcutId).catch(
|
await commands
|
||||||
console.error,
|
.resumeBinding(editingShortcutId)
|
||||||
);
|
.catch(console.error);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to restore original binding:", error);
|
console.error("Failed to restore original binding:", error);
|
||||||
toast.error("Failed to restore original shortcut");
|
toast.error("Failed to restore original shortcut");
|
||||||
}
|
}
|
||||||
} else if (editingShortcutId) {
|
} else if (editingShortcutId) {
|
||||||
commands.resumeBinding(editingShortcutId).catch(
|
commands.resumeBinding(editingShortcutId).catch(console.error);
|
||||||
console.error,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
setEditingShortcutId(null);
|
setEditingShortcutId(null);
|
||||||
setKeyPressed([]);
|
setKeyPressed([]);
|
||||||
|
|
|
||||||
28
src/components/settings/UpdateChecksToggle.tsx
Normal file
28
src/components/settings/UpdateChecksToggle.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React from "react";
|
||||||
|
import { ToggleSwitch } from "../ui/ToggleSwitch";
|
||||||
|
import { useSettings } from "../../hooks/useSettings";
|
||||||
|
|
||||||
|
interface UpdateChecksToggleProps {
|
||||||
|
descriptionMode?: "inline" | "tooltip";
|
||||||
|
grouped?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UpdateChecksToggle: React.FC<UpdateChecksToggleProps> = ({
|
||||||
|
descriptionMode = "tooltip",
|
||||||
|
grouped = false,
|
||||||
|
}) => {
|
||||||
|
const { getSetting, updateSetting, isUpdating } = useSettings();
|
||||||
|
const updateChecksEnabled = getSetting("update_checks_enabled") ?? true;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToggleSwitch
|
||||||
|
checked={updateChecksEnabled}
|
||||||
|
onChange={(enabled) => updateSetting("update_checks_enabled", enabled)}
|
||||||
|
isUpdating={isUpdating("update_checks_enabled")}
|
||||||
|
label="Check for Updates"
|
||||||
|
description="Allow Handy to automatically check for updates and enable manual checks from the footer or tray menu."
|
||||||
|
descriptionMode={descriptionMode}
|
||||||
|
grouped={grouped}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -11,6 +11,7 @@ import { MuteWhileRecording } from "../MuteWhileRecording";
|
||||||
import { RecordingRetentionPeriodSelector } from "../RecordingRetentionPeriod";
|
import { RecordingRetentionPeriodSelector } from "../RecordingRetentionPeriod";
|
||||||
import { ClamshellMicrophoneSelector } from "../ClamshellMicrophoneSelector";
|
import { ClamshellMicrophoneSelector } from "../ClamshellMicrophoneSelector";
|
||||||
import { HandyShortcut } from "../HandyShortcut";
|
import { HandyShortcut } from "../HandyShortcut";
|
||||||
|
import { UpdateChecksToggle } from "../UpdateChecksToggle";
|
||||||
import { useSettings } from "../../../hooks/useSettings";
|
import { useSettings } from "../../../hooks/useSettings";
|
||||||
|
|
||||||
export const DebugSettings: React.FC = () => {
|
export const DebugSettings: React.FC = () => {
|
||||||
|
|
@ -22,6 +23,7 @@ export const DebugSettings: React.FC = () => {
|
||||||
<SettingsGroup title="Debug">
|
<SettingsGroup title="Debug">
|
||||||
<LogDirectory grouped={true} />
|
<LogDirectory grouped={true} />
|
||||||
<LogLevelSelector grouped={true} />
|
<LogLevelSelector grouped={true} />
|
||||||
|
<UpdateChecksToggle descriptionMode="tooltip" grouped={true} />
|
||||||
<SoundPicker
|
<SoundPicker
|
||||||
label="Sound Theme"
|
label="Sound Theme"
|
||||||
description="Choose a sound theme for recording start and stop feedback"
|
description="Choose a sound theme for recording start and stop feedback"
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,4 @@ export { StartHidden } from "./StartHidden";
|
||||||
export { HistoryLimit } from "./HistoryLimit";
|
export { HistoryLimit } from "./HistoryLimit";
|
||||||
export { RecordingRetentionPeriodSelector } from "./RecordingRetentionPeriod";
|
export { RecordingRetentionPeriodSelector } from "./RecordingRetentionPeriod";
|
||||||
export { AutostartToggle } from "./AutostartToggle";
|
export { AutostartToggle } from "./AutostartToggle";
|
||||||
|
export { UpdateChecksToggle } from "./UpdateChecksToggle";
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,7 @@ const PostProcessingSettingsPromptsComponent: React.FC = () => {
|
||||||
try {
|
try {
|
||||||
const result = await commands.addPostProcessPrompt(
|
const result = await commands.addPostProcessPrompt(
|
||||||
draftName.trim(),
|
draftName.trim(),
|
||||||
draftText.trim()
|
draftText.trim(),
|
||||||
);
|
);
|
||||||
if (result.status === "ok") {
|
if (result.status === "ok") {
|
||||||
await refreshSettings();
|
await refreshSettings();
|
||||||
|
|
@ -199,7 +199,7 @@ const PostProcessingSettingsPromptsComponent: React.FC = () => {
|
||||||
await commands.updatePostProcessPrompt(
|
await commands.updatePostProcessPrompt(
|
||||||
selectedPromptId,
|
selectedPromptId,
|
||||||
draftName.trim(),
|
draftName.trim(),
|
||||||
draftText.trim()
|
draftText.trim(),
|
||||||
);
|
);
|
||||||
await refreshSettings();
|
await refreshSettings();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { check } from "@tauri-apps/plugin-updater";
|
||||||
import { relaunch } from "@tauri-apps/plugin-process";
|
import { relaunch } from "@tauri-apps/plugin-process";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { ProgressBar } from "../shared";
|
import { ProgressBar } from "../shared";
|
||||||
|
import { useSettings } from "../../hooks/useSettings";
|
||||||
|
|
||||||
interface UpdateCheckerProps {
|
interface UpdateCheckerProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
@ -16,12 +17,29 @@ const UpdateChecker: React.FC<UpdateCheckerProps> = ({ className = "" }) => {
|
||||||
const [downloadProgress, setDownloadProgress] = useState(0);
|
const [downloadProgress, setDownloadProgress] = useState(0);
|
||||||
const [showUpToDate, setShowUpToDate] = useState(false);
|
const [showUpToDate, setShowUpToDate] = useState(false);
|
||||||
|
|
||||||
|
const { settings, isLoading } = useSettings();
|
||||||
|
const settingsLoaded = !isLoading && settings !== null;
|
||||||
|
const updateChecksEnabled = settings?.update_checks_enabled ?? false;
|
||||||
|
|
||||||
const upToDateTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
const upToDateTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
||||||
const isManualCheckRef = useRef(false);
|
const isManualCheckRef = useRef(false);
|
||||||
const downloadedBytesRef = useRef(0);
|
const downloadedBytesRef = useRef(0);
|
||||||
const contentLengthRef = useRef(0);
|
const contentLengthRef = useRef(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Wait for settings to load before doing anything
|
||||||
|
if (!settingsLoaded) return;
|
||||||
|
|
||||||
|
if (!updateChecksEnabled) {
|
||||||
|
if (upToDateTimeoutRef.current) {
|
||||||
|
clearTimeout(upToDateTimeoutRef.current);
|
||||||
|
}
|
||||||
|
setIsChecking(false);
|
||||||
|
setUpdateAvailable(false);
|
||||||
|
setShowUpToDate(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
checkForUpdates();
|
checkForUpdates();
|
||||||
|
|
||||||
// Listen for update check events
|
// Listen for update check events
|
||||||
|
|
@ -35,11 +53,11 @@ const UpdateChecker: React.FC<UpdateCheckerProps> = ({ className = "" }) => {
|
||||||
}
|
}
|
||||||
updateUnlisten.then((fn) => fn());
|
updateUnlisten.then((fn) => fn());
|
||||||
};
|
};
|
||||||
}, []);
|
}, [settingsLoaded, updateChecksEnabled]);
|
||||||
|
|
||||||
// Update checking functions
|
// Update checking functions
|
||||||
const checkForUpdates = async () => {
|
const checkForUpdates = async () => {
|
||||||
if (isChecking) return;
|
if (!updateChecksEnabled || isChecking) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsChecking(true);
|
setIsChecking(true);
|
||||||
|
|
@ -70,11 +88,13 @@ const UpdateChecker: React.FC<UpdateCheckerProps> = ({ className = "" }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleManualUpdateCheck = () => {
|
const handleManualUpdateCheck = () => {
|
||||||
|
if (!updateChecksEnabled) return;
|
||||||
isManualCheckRef.current = true;
|
isManualCheckRef.current = true;
|
||||||
checkForUpdates();
|
checkForUpdates();
|
||||||
};
|
};
|
||||||
|
|
||||||
const installUpdate = async () => {
|
const installUpdate = async () => {
|
||||||
|
if (!updateChecksEnabled) return;
|
||||||
try {
|
try {
|
||||||
setIsInstalling(true);
|
setIsInstalling(true);
|
||||||
setDownloadProgress(0);
|
setDownloadProgress(0);
|
||||||
|
|
@ -119,6 +139,9 @@ const UpdateChecker: React.FC<UpdateCheckerProps> = ({ className = "" }) => {
|
||||||
|
|
||||||
// Update status functions
|
// Update status functions
|
||||||
const getUpdateStatusText = () => {
|
const getUpdateStatusText = () => {
|
||||||
|
if (!updateChecksEnabled) {
|
||||||
|
return "Update Checking Disabled";
|
||||||
|
}
|
||||||
if (isInstalling) {
|
if (isInstalling) {
|
||||||
return downloadProgress > 0 && downloadProgress < 100
|
return downloadProgress > 0 && downloadProgress < 100
|
||||||
? `Downloading... ${downloadProgress.toString().padStart(3)}%`
|
? `Downloading... ${downloadProgress.toString().padStart(3)}%`
|
||||||
|
|
@ -133,13 +156,14 @@ const UpdateChecker: React.FC<UpdateCheckerProps> = ({ className = "" }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUpdateStatusAction = () => {
|
const getUpdateStatusAction = () => {
|
||||||
|
if (!updateChecksEnabled) return undefined;
|
||||||
if (updateAvailable && !isInstalling) return installUpdate;
|
if (updateAvailable && !isInstalling) return installUpdate;
|
||||||
if (!isChecking && !isInstalling && !updateAvailable)
|
if (!isChecking && !isInstalling && !updateAvailable)
|
||||||
return handleManualUpdateCheck;
|
return handleManualUpdateCheck;
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isUpdateDisabled = isChecking || isInstalling;
|
const isUpdateDisabled = !updateChecksEnabled || isChecking || isInstalling;
|
||||||
const isUpdateClickable =
|
const isUpdateClickable =
|
||||||
!isUpdateDisabled && (updateAvailable || (!isChecking && !showUpToDate));
|
!isUpdateDisabled && (updateAvailable || (!isChecking && !showUpToDate));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,24 +76,28 @@ const settingUpdaters: {
|
||||||
commands.changeAudioFeedbackSetting(value as boolean),
|
commands.changeAudioFeedbackSetting(value as boolean),
|
||||||
audio_feedback_volume: (value) =>
|
audio_feedback_volume: (value) =>
|
||||||
commands.changeAudioFeedbackVolumeSetting(value as number),
|
commands.changeAudioFeedbackVolumeSetting(value as number),
|
||||||
sound_theme: (value) =>
|
sound_theme: (value) => commands.changeSoundThemeSetting(value as string),
|
||||||
commands.changeSoundThemeSetting(value as string),
|
start_hidden: (value) => commands.changeStartHiddenSetting(value as boolean),
|
||||||
start_hidden: (value) =>
|
|
||||||
commands.changeStartHiddenSetting(value as boolean),
|
|
||||||
autostart_enabled: (value) =>
|
autostart_enabled: (value) =>
|
||||||
commands.changeAutostartSetting(value as boolean),
|
commands.changeAutostartSetting(value as boolean),
|
||||||
|
update_checks_enabled: (value) =>
|
||||||
|
commands.changeUpdateChecksSetting(value as boolean),
|
||||||
push_to_talk: (value) => commands.changePttSetting(value as boolean),
|
push_to_talk: (value) => commands.changePttSetting(value as boolean),
|
||||||
selected_microphone: (value) =>
|
selected_microphone: (value) =>
|
||||||
commands.setSelectedMicrophone(
|
commands.setSelectedMicrophone(
|
||||||
(value as string) === "Default" || value === null ? "default" : (value as string)
|
(value as string) === "Default" || value === null
|
||||||
|
? "default"
|
||||||
|
: (value as string),
|
||||||
),
|
),
|
||||||
clamshell_microphone: (value) =>
|
clamshell_microphone: (value) =>
|
||||||
commands.setClamshellMicrophone(
|
commands.setClamshellMicrophone(
|
||||||
(value as string) === "Default" ? "default" : (value as string)
|
(value as string) === "Default" ? "default" : (value as string),
|
||||||
),
|
),
|
||||||
selected_output_device: (value) =>
|
selected_output_device: (value) =>
|
||||||
commands.setSelectedOutputDevice(
|
commands.setSelectedOutputDevice(
|
||||||
(value as string) === "Default" || value === null ? "default" : (value as string)
|
(value as string) === "Default" || value === null
|
||||||
|
? "default"
|
||||||
|
: (value as string),
|
||||||
),
|
),
|
||||||
recording_retention_period: (value) =>
|
recording_retention_period: (value) =>
|
||||||
commands.updateRecordingRetentionPeriod(value as string),
|
commands.updateRecordingRetentionPeriod(value as string),
|
||||||
|
|
@ -103,13 +107,11 @@ const settingUpdaters: {
|
||||||
commands.changeSelectedLanguageSetting(value as string),
|
commands.changeSelectedLanguageSetting(value as string),
|
||||||
overlay_position: (value) =>
|
overlay_position: (value) =>
|
||||||
commands.changeOverlayPositionSetting(value as string),
|
commands.changeOverlayPositionSetting(value as string),
|
||||||
debug_mode: (value) =>
|
debug_mode: (value) => commands.changeDebugModeSetting(value as boolean),
|
||||||
commands.changeDebugModeSetting(value as boolean),
|
|
||||||
custom_words: (value) => commands.updateCustomWords(value as string[]),
|
custom_words: (value) => commands.updateCustomWords(value as string[]),
|
||||||
word_correction_threshold: (value) =>
|
word_correction_threshold: (value) =>
|
||||||
commands.changeWordCorrectionThresholdSetting(value as number),
|
commands.changeWordCorrectionThresholdSetting(value as number),
|
||||||
paste_method: (value) =>
|
paste_method: (value) => commands.changePasteMethodSetting(value as string),
|
||||||
commands.changePasteMethodSetting(value as string),
|
|
||||||
clipboard_handling: (value) =>
|
clipboard_handling: (value) =>
|
||||||
commands.changeClipboardHandlingSetting(value as string),
|
commands.changeClipboardHandlingSetting(value as string),
|
||||||
history_limit: (value) => commands.updateHistoryLimit(value as string),
|
history_limit: (value) => commands.updateHistoryLimit(value as string),
|
||||||
|
|
@ -158,10 +160,8 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
const normalizedSettings: Settings = {
|
const normalizedSettings: Settings = {
|
||||||
...settings,
|
...settings,
|
||||||
always_on_microphone: settings.always_on_microphone ?? false,
|
always_on_microphone: settings.always_on_microphone ?? false,
|
||||||
selected_microphone:
|
selected_microphone: settings.selected_microphone ?? "Default",
|
||||||
settings.selected_microphone ?? "Default",
|
clamshell_microphone: settings.clamshell_microphone ?? "Default",
|
||||||
clamshell_microphone:
|
|
||||||
settings.clamshell_microphone ?? "Default",
|
|
||||||
selected_output_device:
|
selected_output_device:
|
||||||
settings.selected_output_device ?? "Default",
|
settings.selected_output_device ?? "Default",
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue