diff --git a/android/app/src/main/kotlin/app/cogwheel/conduit/ConduitVoiceInteractionSession.kt b/android/app/src/main/kotlin/app/cogwheel/conduit/ConduitVoiceInteractionSession.kt index 178c66c..afd6926 100644 --- a/android/app/src/main/kotlin/app/cogwheel/conduit/ConduitVoiceInteractionSession.kt +++ b/android/app/src/main/kotlin/app/cogwheel/conduit/ConduitVoiceInteractionSession.kt @@ -10,10 +10,29 @@ import android.graphics.Bitmap class ConduitVoiceInteractionSession(context: Context) : VoiceInteractionSession(context) { + companion object { + private const val PREFS_FILE = "FlutterSharedPreferences" + private const val TRIGGER_KEY = "flutter.android_assistant_trigger" + private const val TRIGGER_OVERLAY = "overlay" + private const val TRIGGER_NEW_CHAT = "new_chat" + private const val TRIGGER_VOICE_CALL = "voice_call" + } + private var capturedContext: String? = null private var capturedScreenshot: Bitmap? = null override fun onCreateContentView(): android.view.View { + when (getTriggerPreference()) { + TRIGGER_NEW_CHAT -> { + launchAppForNewChat() + return android.view.View(context) + } + TRIGGER_VOICE_CALL -> { + launchAppForVoiceCall() + return android.view.View(context) + } + } + val view = layoutInflater.inflate(app.cogwheel.conduit.R.layout.assistant_overlay, null) // Summarize page button - sends screen context @@ -157,6 +176,25 @@ class ConduitVoiceInteractionSession(context: Context) : VoiceInteractionSession } } + private fun launchAppForNewChat() { + try { + android.util.Log.d("ConduitVoiceSession", "Attempting to launch app for new chat") + val intent = Intent(context, MainActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + + intent.putExtra("start_new_chat", true) + android.util.Log.d("ConduitVoiceSession", "New chat flag attached") + + context.startActivity(intent) + android.util.Log.d("ConduitVoiceSession", "App launch requested for new chat") + finish() + } catch (e: Exception) { + android.util.Log.e("ConduitVoiceSession", "Failed to launch app for new chat", e) + } + } + private fun launchAppForVoiceCall() { try { android.util.Log.d("ConduitVoiceSession", "Attempting to launch app for voice call") @@ -176,6 +214,15 @@ class ConduitVoiceInteractionSession(context: Context) : VoiceInteractionSession } } + private fun getTriggerPreference(): String { + return try { + val prefs = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE) + prefs.getString(TRIGGER_KEY, TRIGGER_OVERLAY) ?: TRIGGER_OVERLAY + } catch (e: Exception) { + TRIGGER_OVERLAY + } + } + private fun traverseNode(node: AssistStructure.ViewNode?, builder: StringBuilder) { if (node == null) return diff --git a/android/app/src/main/kotlin/app/cogwheel/conduit/MainActivity.kt b/android/app/src/main/kotlin/app/cogwheel/conduit/MainActivity.kt index e2cb63f..149c004 100644 --- a/android/app/src/main/kotlin/app/cogwheel/conduit/MainActivity.kt +++ b/android/app/src/main/kotlin/app/cogwheel/conduit/MainActivity.kt @@ -51,15 +51,20 @@ class MainActivity : FlutterActivity() { val screenContext = intent.getStringExtra("screen_context") val screenshotPath = intent.getStringExtra("screenshot_path") val startVoiceCall = intent.getBooleanExtra("start_voice_call", false) + val startNewChat = intent.getBooleanExtra("start_new_chat", false) android.util.Log.d("MainActivity", "screenContext: $screenContext") android.util.Log.d("MainActivity", "screenshotPath: $screenshotPath") android.util.Log.d("MainActivity", "startVoiceCall: $startVoiceCall") + android.util.Log.d("MainActivity", "startNewChat: $startNewChat") android.util.Log.d("MainActivity", "methodChannel: $methodChannel") if (startVoiceCall) { android.util.Log.d("MainActivity", "Invoking startVoiceCall") methodChannel?.invokeMethod("startVoiceCall", null) + } else if (startNewChat) { + android.util.Log.d("MainActivity", "Invoking startNewChat") + methodChannel?.invokeMethod("startNewChat", null) } else if (screenContext != null) { android.util.Log.d("MainActivity", "Invoking analyzeScreen") methodChannel?.invokeMethod("analyzeScreen", screenContext) diff --git a/lib/core/persistence/persistence_keys.dart b/lib/core/persistence/persistence_keys.dart index c74a616..faacb45 100644 --- a/lib/core/persistence/persistence_keys.dart +++ b/lib/core/persistence/persistence_keys.dart @@ -29,6 +29,7 @@ final class PreferenceKeys { static const String ttsServerVoiceId = 'tts_server_voice_id'; static const String ttsServerVoiceName = 'tts_server_voice_name'; static const String voiceSilenceDuration = 'voice_silence_duration'; + static const String androidAssistantTrigger = 'android_assistant_trigger'; } final class LegacyPreferenceKeys { diff --git a/lib/core/services/settings_service.dart b/lib/core/services/settings_service.dart index d34d180..fd567d0 100644 --- a/lib/core/services/settings_service.dart +++ b/lib/core/services/settings_service.dart @@ -3,6 +3,7 @@ import 'dart:developer' as developer; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:hive_ce/hive.dart'; import '../persistence/hive_bootstrap.dart'; import '../persistence/hive_boxes.dart'; @@ -17,6 +18,22 @@ enum SttPreference { deviceOnly, serverOnly } /// TTS engine selection enum TtsEngine { device, server } +/// Action to take when the Android digital assistant is triggered. +enum AndroidAssistantTrigger { overlay, newChat, voiceCall } + +extension AndroidAssistantTriggerStorage on AndroidAssistantTrigger { + String get storageValue { + switch (this) { + case AndroidAssistantTrigger.overlay: + return 'overlay'; + case AndroidAssistantTrigger.newChat: + return 'new_chat'; + case AndroidAssistantTrigger.voiceCall: + return 'voice_call'; + } + } +} + /// Service for managing app-wide settings including accessibility preferences class SettingsService { static const String _reduceMotionKey = PreferenceKeys.reduceMotion; @@ -41,6 +58,8 @@ class SettingsService { // Voice silence duration for auto-stop (milliseconds) static const String _voiceSilenceDurationKey = PreferenceKeys.voiceSilenceDuration; + static const String _androidAssistantTriggerKey = + PreferenceKeys.androidAssistantTrigger; static Box _preferencesBox() => Hive.box(HiveBoxNames.preferences); @@ -153,6 +172,8 @@ class SettingsService { PreferenceKeys.ttsEngine: settings.ttsEngine.name, PreferenceKeys.voiceSttPreference: settings.sttPreference.name, _voiceSilenceDurationKey: settings.voiceSilenceDuration, + _androidAssistantTriggerKey: + settings.androidAssistantTrigger.storageValue, }; await box.putAll(updates); @@ -191,6 +212,8 @@ class SettingsService { } else { await box.delete(PreferenceKeys.ttsServerVoiceName); } + + await _writeAssistantTriggerToSharedPrefs(settings.androidAssistantTrigger); } static TtsEngine _parseTtsEngine(String? raw) { @@ -219,6 +242,20 @@ class SettingsService { } } + static AndroidAssistantTrigger _parseAndroidAssistantTrigger(String? raw) { + switch ((raw ?? '').toLowerCase()) { + case 'new_chat': + case 'newchat': + return AndroidAssistantTrigger.newChat; + case 'voice_call': + case 'voicecall': + return AndroidAssistantTrigger.voiceCall; + case 'overlay': + default: + return AndroidAssistantTrigger.overlay; + } + } + // Voice input specific settings static Future getVoiceLocaleId() { final value = _preferencesBox().get(_voiceLocaleKey) as String?; @@ -309,6 +346,30 @@ class SettingsService { return _preferencesBox().put(_voiceSilenceDurationKey, sanitized); } + static Future setAndroidAssistantTrigger( + AndroidAssistantTrigger trigger, + ) async { + await _preferencesBox().put( + _androidAssistantTriggerKey, + trigger.storageValue, + ); + await _writeAssistantTriggerToSharedPrefs(trigger); + } + + static Future _writeAssistantTriggerToSharedPrefs( + AndroidAssistantTrigger trigger, + ) async { + try { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString( + PreferenceKeys.androidAssistantTrigger, + trigger.storageValue, + ); + } catch (_) { + // SharedPreferences writes are best-effort for Android assistant access + } + } + /// Get effective animation duration considering all settings static Duration getEffectiveAnimationDuration( BuildContext context, @@ -372,6 +433,9 @@ class SettingsService { sttPreference: _parseSttPreference( box.get(PreferenceKeys.voiceSttPreference) as String?, ), + androidAssistantTrigger: _parseAndroidAssistantTrigger( + box.get(_androidAssistantTriggerKey) as String?, + ), voiceSilenceDuration: (box.get(_voiceSilenceDurationKey) as int? ?? 2000) .clamp(300, 3000), ); @@ -406,6 +470,7 @@ class AppSettings { final TtsEngine ttsEngine; final String? ttsServerVoiceId; final String? ttsServerVoiceName; + final AndroidAssistantTrigger androidAssistantTrigger; final int voiceSilenceDuration; const AppSettings({ this.reduceMotion = false, @@ -429,6 +494,7 @@ class AppSettings { this.ttsEngine = TtsEngine.device, this.ttsServerVoiceId, this.ttsServerVoiceName, + this.androidAssistantTrigger = AndroidAssistantTrigger.overlay, this.voiceSilenceDuration = 2000, }); @@ -455,6 +521,7 @@ class AppSettings { Object? ttsServerVoiceId = const _DefaultValue(), Object? ttsServerVoiceName = const _DefaultValue(), int? voiceSilenceDuration, + AndroidAssistantTrigger? androidAssistantTrigger, }) { return AppSettings( reduceMotion: reduceMotion ?? this.reduceMotion, @@ -486,6 +553,8 @@ class AppSettings { ttsServerVoiceName: ttsServerVoiceName is _DefaultValue ? this.ttsServerVoiceName : ttsServerVoiceName as String?, + androidAssistantTrigger: + androidAssistantTrigger ?? this.androidAssistantTrigger, voiceSilenceDuration: voiceSilenceDuration ?? this.voiceSilenceDuration, ); } @@ -513,6 +582,7 @@ class AppSettings { other.ttsEngine == ttsEngine && other.ttsServerVoiceId == ttsServerVoiceId && other.ttsServerVoiceName == ttsServerVoiceName && + other.androidAssistantTrigger == androidAssistantTrigger && other.voiceSilenceDuration == voiceSilenceDuration && _listEquals(other.quickPills, quickPills); // socketTransportMode intentionally not included in == to avoid frequent rebuilds @@ -541,6 +611,7 @@ class AppSettings { ttsEngine, ttsServerVoiceId, ttsServerVoiceName, + androidAssistantTrigger, voiceSilenceDuration, Object.hashAllUnordered(quickPills), ]); @@ -715,6 +786,16 @@ class AppSettingsNotifier extends _$AppSettingsNotifier { await SettingsService.setVoiceSilenceDuration(milliseconds); } + Future setAndroidAssistantTrigger( + AndroidAssistantTrigger trigger, + ) async { + if (state.androidAssistantTrigger == trigger) { + return; + } + state = state.copyWith(androidAssistantTrigger: trigger); + await SettingsService.setAndroidAssistantTrigger(trigger); + } + Future resetToDefaults() async { const defaultSettings = AppSettings(); await SettingsService.saveSettings(defaultSettings); diff --git a/lib/core/utils/android_assistant_handler.dart b/lib/core/utils/android_assistant_handler.dart index 4b03461..2a14345 100644 --- a/lib/core/utils/android_assistant_handler.dart +++ b/lib/core/utils/android_assistant_handler.dart @@ -46,19 +46,27 @@ class AndroidAssistantHandler { await _processScreenshot(screenshotPath); } else if (call.method == 'startVoiceCall') { await _startVoiceCall(); + } else if (call.method == 'startNewChat') { + await _startNewChat(); } } Future _processScreenshot(String screenshotPath) async { try { - DebugLogger.log('Processing screenshot: $screenshotPath', scope: 'assistant'); + DebugLogger.log( + 'Processing screenshot: $screenshotPath', + scope: 'assistant', + ); // Wait for app to be ready (authenticated and model available) final navState = _ref.read(authNavigationStateProvider); final model = _ref.read(selectedModelProvider); if (navState != AuthNavigationState.authenticated || model == null) { - DebugLogger.log('App not ready for screenshot processing', scope: 'assistant'); + DebugLogger.log( + 'App not ready for screenshot processing', + scope: 'assistant', + ); return; } @@ -75,7 +83,10 @@ class AndroidAssistantHandler { // Add screenshot as attachment final file = File(screenshotPath); if (!await file.exists()) { - DebugLogger.log('Screenshot file not found: $screenshotPath', scope: 'assistant'); + DebugLogger.log( + 'Screenshot file not found: $screenshotPath', + scope: 'assistant', + ); return; } @@ -91,15 +102,23 @@ class AndroidAssistantHandler { // Enqueue upload via task queue final activeConv = _ref.read(activeConversationProvider); try { - await _ref.read(taskQueueProvider.notifier).enqueueUploadMedia( + await _ref + .read(taskQueueProvider.notifier) + .enqueueUploadMedia( conversationId: activeConv?.id, filePath: attachment.file.path, fileName: attachment.displayName, fileSize: await attachment.file.length(), ); - DebugLogger.log('Screenshot uploaded successfully', scope: 'assistant'); + DebugLogger.log( + 'Screenshot uploaded successfully', + scope: 'assistant', + ); } catch (e) { - DebugLogger.log('Failed to upload screenshot: $e', scope: 'assistant'); + DebugLogger.log( + 'Failed to upload screenshot: $e', + scope: 'assistant', + ); } } } catch (e) { @@ -130,7 +149,10 @@ class AndroidAssistantHandler { // Get the current BuildContext from the navigation service final context = NavigationService.navigatorKey.currentContext; if (context == null) { - DebugLogger.log('No context available for voice call navigation', scope: 'assistant'); + DebugLogger.log( + 'No context available for voice call navigation', + scope: 'assistant', + ); return; } @@ -147,4 +169,28 @@ class AndroidAssistantHandler { DebugLogger.log('Failed to start voice call: $e', scope: 'assistant'); } } + + Future _startNewChat() async { + try { + DebugLogger.log('Starting new chat from assistant', scope: 'assistant'); + + final navState = _ref.read(authNavigationStateProvider); + final model = _ref.read(selectedModelProvider); + + if (navState != AuthNavigationState.authenticated || model == null) { + DebugLogger.log('App not ready for new chat', scope: 'assistant'); + return; + } + + final isOnChatRoute = NavigationService.currentRoute == Routes.chat; + if (!isOnChatRoute) { + await NavigationService.navigateToChat(); + } + + startNewChat(_ref); + DebugLogger.log('New chat started from assistant', scope: 'assistant'); + } catch (e) { + DebugLogger.log('Failed to start new chat: $e', scope: 'assistant'); + } + } } diff --git a/lib/features/profile/views/app_customization_page.dart b/lib/features/profile/views/app_customization_page.dart index b4f0c58..9178f9c 100644 --- a/lib/features/profile/views/app_customization_page.dart +++ b/lib/features/profile/views/app_customization_page.dart @@ -407,6 +407,10 @@ class AppCustomizationPage extends ConsumerWidget { final transportLabel = activeTransportMode == 'polling' ? l10n.transportModePolling : l10n.transportModeWs; + final assistantTriggerLabel = _androidAssistantTriggerLabel( + l10n, + settings.androidAssistantTrigger, + ); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -467,10 +471,164 @@ class AppCustomizationPage extends ConsumerWidget { .read(appSettingsProvider.notifier) .setSendOnEnter(!settings.sendOnEnter), ), + if (Platform.isAndroid) ...[ + const SizedBox(height: Spacing.sm), + _CustomizationTile( + leading: _buildIconBadge( + context, + Icons.assistant, + color: theme.buttonPrimary, + ), + title: l10n.androidAssistantTitle, + subtitle: assistantTriggerLabel, + onTap: () => + _showAndroidAssistantTriggerSheet(context, ref, settings), + ), + ], ], ); } + String _androidAssistantTriggerLabel( + AppLocalizations l10n, + AndroidAssistantTrigger trigger, + ) { + switch (trigger) { + case AndroidAssistantTrigger.overlay: + return l10n.androidAssistantOverlayOption; + case AndroidAssistantTrigger.newChat: + return l10n.androidAssistantNewChatOption; + case AndroidAssistantTrigger.voiceCall: + return l10n.androidAssistantVoiceCallOption; + } + } + + Future _showAndroidAssistantTriggerSheet( + BuildContext context, + WidgetRef ref, + AppSettings settings, + ) async { + final theme = context.conduitTheme; + final l10n = AppLocalizations.of(context)!; + final options = <({AndroidAssistantTrigger value, String label})>[ + ( + value: AndroidAssistantTrigger.overlay, + label: l10n.androidAssistantOverlayOption, + ), + ( + value: AndroidAssistantTrigger.newChat, + label: l10n.androidAssistantNewChatOption, + ), + ( + value: AndroidAssistantTrigger.voiceCall, + label: l10n.androidAssistantVoiceCallOption, + ), + ]; + + await showModalBottomSheet( + context: context, + backgroundColor: theme.sidebarBackground, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(AppBorderRadius.modal), + ), + ), + builder: (sheetContext) { + return SafeArea( + top: false, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: Spacing.lg, + vertical: Spacing.md, + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.androidAssistantTitle, + style: + theme.headingSmall?.copyWith( + color: theme.sidebarForeground, + ) ?? + TextStyle( + color: theme.sidebarForeground, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: Spacing.xs), + Text( + l10n.androidAssistantDescription, + style: + theme.bodySmall?.copyWith( + color: theme.sidebarForeground.withValues( + alpha: 0.7, + ), + ) ?? + TextStyle( + color: theme.sidebarForeground.withValues( + alpha: 0.7, + ), + ), + ), + ], + ), + ), + IconButton( + icon: Icon(Icons.close, color: theme.iconPrimary), + onPressed: () => Navigator.of(sheetContext).pop(), + ), + ], + ), + ), + const Divider(height: 1), + for (var i = 0; i < options.length; i++) ...[ + () { + final option = options[i]; + final selected = + settings.androidAssistantTrigger == option.value; + return ListTile( + leading: Icon( + selected ? Icons.check_circle : Icons.circle_outlined, + color: selected + ? theme.buttonPrimary + : theme.iconSecondary, + ), + title: Text( + option.label, + style: theme.bodyMedium?.copyWith( + color: theme.sidebarForeground, + fontWeight: selected + ? FontWeight.w600 + : FontWeight.w500, + ), + ), + onTap: () { + if (!selected) { + ref + .read(appSettingsProvider.notifier) + .setAndroidAssistantTrigger(option.value); + } + Navigator.of(sheetContext).pop(); + }, + ); + }(), + if (i != options.length - 1) const Divider(height: 1), + ], + const SizedBox(height: Spacing.lg), + ], + ), + ); + }, + ); + } + Widget _buildSttSection( BuildContext context, WidgetRef ref, diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 5c1218c..ceb2a51 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -308,6 +308,11 @@ "chatSettings": "Chat", "sendOnEnter": "Mit Enter senden", "sendOnEnterDescription": "Enter sendet (Soft-Tastatur). Cmd/Ctrl+Enter ebenfalls verfügbar", + "androidAssistantTitle": "Android digital assistant", + "androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.", + "androidAssistantOverlayOption": "Show quick overlay (default)", + "androidAssistantNewChatOption": "Open Conduit with a new chat", + "androidAssistantVoiceCallOption": "Start a voice call", "sttSettings": "Sprache zu Text", "sttEngineLabel": "Erkennungs-Engine", "sttEngineDevice": "Auf dem Gerät", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index a6cee1c..a03f938 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1223,6 +1223,26 @@ "@sendOnEnterDescription": { "description": "Explanation of how the Send on Enter toggle behaves." }, + "androidAssistantTitle": "Android digital assistant", + "@androidAssistantTitle": { + "description": "Tile title for configuring the Android digital assistant trigger." + }, + "androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.", + "@androidAssistantDescription": { + "description": "Helper text describing the Android digital assistant trigger setting." + }, + "androidAssistantOverlayOption": "Show quick overlay (default)", + "@androidAssistantOverlayOption": { + "description": "Option label for keeping the current assistant overlay." + }, + "androidAssistantNewChatOption": "Open Conduit with a new chat", + "@androidAssistantNewChatOption": { + "description": "Option label for opening the app to a fresh chat from the assistant trigger." + }, + "androidAssistantVoiceCallOption": "Start a voice call", + "@androidAssistantVoiceCallOption": { + "description": "Option label for jumping straight into voice call from assistant trigger." + }, "sttSettings": "Speech to Text", "@sttSettings": { "description": "Section header for speech-to-text settings." diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 085f02d..3f8b0d2 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -308,6 +308,11 @@ "chatSettings": "Conversación", "sendOnEnter": "Enviar con Enter", "sendOnEnterDescription": "Enter envía (teclado virtual). Cmd/Ctrl+Enter también disponible", + "androidAssistantTitle": "Android digital assistant", + "androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.", + "androidAssistantOverlayOption": "Show quick overlay (default)", + "androidAssistantNewChatOption": "Open Conduit with a new chat", + "androidAssistantVoiceCallOption": "Start a voice call", "sttSettings": "Voz a texto", "sttEngineLabel": "Motor de reconocimiento", "sttEngineDevice": "En el dispositivo", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index ca3d0aa..bcc5610 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -308,6 +308,11 @@ "chatSettings": "Discussion", "sendOnEnter": "Envoyer avec Entrée", "sendOnEnterDescription": "Entrée envoie (clavier logiciel). Cmd/Ctrl+Entrée aussi disponible", + "androidAssistantTitle": "Android digital assistant", + "androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.", + "androidAssistantOverlayOption": "Show quick overlay (default)", + "androidAssistantNewChatOption": "Open Conduit with a new chat", + "androidAssistantVoiceCallOption": "Start a voice call", "sttSettings": "Voix vers texte", "sttEngineLabel": "Moteur de reconnaissance", "sttEngineDevice": "Sur l’appareil", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index ebeec67..e62a8c9 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -308,6 +308,11 @@ "chatSettings": "Chat", "sendOnEnter": "Invia con Invio", "sendOnEnterDescription": "Invio invia (tastiera software). Cmd/Ctrl+Invio disponibile", + "androidAssistantTitle": "Android digital assistant", + "androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.", + "androidAssistantOverlayOption": "Show quick overlay (default)", + "androidAssistantNewChatOption": "Open Conduit with a new chat", + "androidAssistantVoiceCallOption": "Start a voice call", "sttSettings": "Voce in testo", "sttEngineLabel": "Motore di riconoscimento", "sttEngineDevice": "Sul dispositivo", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index e090c69..b4e22e9 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -434,6 +434,11 @@ "chatSettings": "채팅", "sendOnEnter": "Enter로 전송", "sendOnEnterDescription": "Enter로 전송 (소프트 키보드). Cmd/Ctrl+Enter도 사용 가능", + "androidAssistantTitle": "Android digital assistant", + "androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.", + "androidAssistantOverlayOption": "Show quick overlay (default)", + "androidAssistantNewChatOption": "Open Conduit with a new chat", + "androidAssistantVoiceCallOption": "Start a voice call", "sttSettings": "음성 텍스트 변환", "sttEngineLabel": "인식 엔진", "sttEngineDevice": "기기에서", diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index dba7da0..6ea69fe 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -308,6 +308,11 @@ "chatSettings": "Chat", "sendOnEnter": "Verzenden met Enter", "sendOnEnterDescription": "Enter verzendt (softtoetsenbord). Cmd/Ctrl+Enter ook beschikbaar", + "androidAssistantTitle": "Android digital assistant", + "androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.", + "androidAssistantOverlayOption": "Show quick overlay (default)", + "androidAssistantNewChatOption": "Open Conduit with a new chat", + "androidAssistantVoiceCallOption": "Start a voice call", "sttSettings": "Spraak naar tekst", "sttEngineLabel": "Herkenningsengine", "sttEngineDevice": "Op het apparaat", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 111cfdb..189f8dc 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -308,6 +308,11 @@ "chatSettings": "Чат", "sendOnEnter": "Отправка по Enter", "sendOnEnterDescription": "Enter отправляет (программная клавиатура). Также доступно Cmd/Ctrl+Enter", + "androidAssistantTitle": "Android digital assistant", + "androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.", + "androidAssistantOverlayOption": "Show quick overlay (default)", + "androidAssistantNewChatOption": "Open Conduit with a new chat", + "androidAssistantVoiceCallOption": "Start a voice call", "sttSettings": "Речь в текст", "sttEngineLabel": "Движок распознавания", "sttEngineDevice": "На устройстве", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 5ea2e1b..37ea494 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -308,6 +308,11 @@ "chatSettings": "对话", "sendOnEnter": "回车发送", "sendOnEnterDescription": "回车发送(软键盘)。Cmd/Ctrl+Enter 也可用", + "androidAssistantTitle": "Android digital assistant", + "androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.", + "androidAssistantOverlayOption": "Show quick overlay (default)", + "androidAssistantNewChatOption": "Open Conduit with a new chat", + "androidAssistantVoiceCallOption": "Start a voice call", "sttSettings": "语音转文字", "sttEngineLabel": "识别引擎", "sttEngineDevice": "本机", diff --git a/lib/l10n/app_zh_Hant.arb b/lib/l10n/app_zh_Hant.arb index 29cda54..7c69c31 100644 --- a/lib/l10n/app_zh_Hant.arb +++ b/lib/l10n/app_zh_Hant.arb @@ -308,6 +308,11 @@ "chatSettings": "對話", "sendOnEnter": "回車發送", "sendOnEnterDescription": "回車發送(軟鍵盤)。Cmd/Ctrl+Enter 也可用", + "androidAssistantTitle": "Android digital assistant", + "androidAssistantDescription": "Choose what happens when you trigger the Android digital assistant.", + "androidAssistantOverlayOption": "Show quick overlay (default)", + "androidAssistantNewChatOption": "Open Conduit with a new chat", + "androidAssistantVoiceCallOption": "Start a voice call", "sttSettings": "語音轉文字", "sttEngineLabel": "識別引擎", "sttEngineDevice": "本機",