From 8f11df460a247dfcaafd84ce3046605724d11998 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Mon, 18 May 2026 17:20:11 +0800
Subject: [PATCH] feat: add hotkey input mode setting

---
 VoiceSnapGo/frontend/src/components/settings/GeneralPage.svelte |   74 ++++++++++++++++++++++++++++++++++++-
 1 files changed, 72 insertions(+), 2 deletions(-)

diff --git a/VoiceSnapGo/frontend/src/components/settings/GeneralPage.svelte b/VoiceSnapGo/frontend/src/components/settings/GeneralPage.svelte
index 091a855..d54ca49 100755
--- a/VoiceSnapGo/frontend/src/components/settings/GeneralPage.svelte
+++ b/VoiceSnapGo/frontend/src/components/settings/GeneralPage.svelte
@@ -2,7 +2,7 @@
   import { Call } from '@wailsio/runtime'
   import ToggleSwitch from '../shared/ToggleSwitch.svelte'
   import { t } from '../../lib/i18n'
-  import { autoHide, soundFeedback, copyToClipboard, startAtLogin, hotkeyVK } from '../../lib/stores/config'
+  import { autoHide, soundFeedback, copyToClipboard, startAtLogin, hotkeyVK, hotkeyMode } from '../../lib/stores/config'
   import { deviceName, engineStatus, engineHardwareInfo } from '../../lib/stores/app'
   import { hotkeyName } from '../../lib/stores/indicator'
 
@@ -21,6 +21,7 @@
   let status = $state('loading')
   let hwInfo = $state('')
   let currentKeyName = $state('Ctrl')
+  let hotkeyModeVal: 'tap' | 'hold' = $state('hold')
   let isRecording = $state(false)
   let hintText = $state('')
   let devices = $state<InputDevice[]>([])
@@ -34,6 +35,7 @@
   const unsub6 = hotkeyName.subscribe(k => { currentKeyName = k })
   const unsub7 = soundFeedback.subscribe(v => { soundFeedbackVal = v })
   const unsub8 = copyToClipboard.subscribe(v => { copyToClipboardVal = v })
+  const unsub9 = hotkeyMode.subscribe(v => { hotkeyModeVal = v })
 
   // Load actual device name from backend on mount
   async function loadDeviceName() {
@@ -69,6 +71,12 @@
       copyToClipboardVal = copy
     } catch {}
     try {
+      const mode: any = await Call.ByName('voicesnap/services.ConfigService.GetHotkeyMode')
+      const normalized = mode === 'tap' ? 'tap' : 'hold'
+      hotkeyMode.set(normalized)
+      hotkeyModeVal = normalized
+    } catch {}
+    try {
       const hideDock: any = await Call.ByName('voicesnap/services.ConfigService.GetHideDockIcon')
       hideDockIconVal = !!hideDock
     } catch {}
@@ -94,6 +102,14 @@
     copyToClipboard.set(checked)
     try {
       await Call.ByName('voicesnap/services.ConfigService.SetCopyToClipboard', checked)
+    } catch {}
+  }
+
+  async function onHotkeyModeChange(mode: 'tap' | 'hold') {
+    hotkeyMode.set(mode)
+    hotkeyModeVal = mode
+    try {
+      await Call.ByName('voicesnap/services.ConfigService.SetHotkeyMode', mode)
     } catch {}
   }
 
@@ -212,7 +228,7 @@
   <!-- Header -->
   <div class="header">
     <h1 class="tagline">{t('home.tagline')}</h1>
-    <p class="app-desc">{t('home.modeHint')}</p>
+    <p class="app-desc">{hotkeyModeVal === 'tap' ? t('home.modeHintTap') : t('home.modeHintHold')}</p>
   </div>
 
   <!-- Hotkey -->
@@ -232,6 +248,31 @@
     {#if hintText}
       <p class="hint" class:recording={isRecording}>{hintText}</p>
     {/if}
+
+    <div class="divider"></div>
+
+    <div class="setting-row">
+      <div class="setting-info">
+        <span class="setting-label">{t('settings.hotkeyMode')}</span>
+        <span class="setting-desc">
+          {hotkeyModeVal === 'tap' ? t('settings.hotkeyModeTapDesc') : t('settings.hotkeyModeHoldDesc')}
+        </span>
+      </div>
+      <div class="segmented" role="group" aria-label={t('settings.hotkeyMode')}>
+        <button
+          class:active={hotkeyModeVal === 'tap'}
+          onclick={() => onHotkeyModeChange('tap')}
+        >
+          {t('settings.hotkeyModeTap')}
+        </button>
+        <button
+          class:active={hotkeyModeVal === 'hold'}
+          onclick={() => onHotkeyModeChange('hold')}
+        >
+          {t('settings.hotkeyModeHold')}
+        </button>
+      </div>
+    </div>
 
   </div>
 
@@ -476,6 +517,35 @@
     color: var(--color-tertiary-label);
   }
 
+  .segmented {
+    display: inline-grid;
+    grid-template-columns: 1fr 1fr;
+    min-width: 148px;
+    padding: 2px;
+    border-radius: var(--radius-sm);
+    background: var(--color-bg-secondary);
+    flex-shrink: 0;
+  }
+
+  .segmented button {
+    min-width: 66px;
+    height: 30px;
+    padding: 0 12px;
+    border: none;
+    border-radius: calc(var(--radius-sm) - 2px);
+    background: transparent;
+    color: var(--color-secondary-label);
+    font-size: var(--font-size-sm);
+    font-weight: 500;
+    cursor: pointer;
+    transition: background var(--transition-fast), color var(--transition-fast);
+  }
+
+  .segmented button.active {
+    background: var(--color-blue);
+    color: white;
+  }
+
   .divider {
     height: 1px;
     background: var(--color-separator);

--
Gitblit v1.9.3