From 761e548ff8862f4fbfb6921960afde76054eb8d3 Mon Sep 17 00:00:00 2001 From: Rogier Muller Date: Sat, 16 May 2026 15:44:10 +0200 Subject: [PATCH 1/2] Add configurable Omi button actions --- app/lib/backend/preferences.dart | 25 ++- app/lib/models/omi_button_action.dart | 36 ++++ app/lib/pages/settings/device_settings.dart | 177 +++++++++++++----- app/lib/providers/capture_provider.dart | 195 ++++++++++++-------- app/test/unit/omi_button_action_test.dart | 49 +++++ omi/firmware/devkit/src/button.c | 99 +++++----- omi/firmware/omi/src/lib/core/button.c | 100 +++++----- 7 files changed, 456 insertions(+), 225 deletions(-) create mode 100644 app/lib/models/omi_button_action.dart create mode 100644 app/test/unit/omi_button_action_test.dart diff --git a/app/lib/backend/preferences.dart b/app/lib/backend/preferences.dart index eb38a99b0a2..d87b5a11e61 100644 --- a/app/lib/backend/preferences.dart +++ b/app/lib/backend/preferences.dart @@ -9,6 +9,7 @@ import 'package:omi/backend/schema/conversation.dart'; import 'package:omi/backend/schema/memory.dart'; import 'package:omi/backend/schema/message.dart'; import 'package:omi/backend/schema/person.dart'; +import 'package:omi/models/omi_button_action.dart'; import 'package:omi/models/custom_stt_config.dart'; import 'package:omi/models/stt_provider.dart'; import 'package:omi/utils/logger.dart'; @@ -45,8 +46,10 @@ class SharedPreferencesUtil { } BtDevice get btDevice { - final String device = getString('btDevice') ?? ''; - if (device.isEmpty) return BtDevice(id: '', name: '', type: DeviceType.omi, rssi: 0); + final String device = getString('btDevice'); + if (device.isEmpty) { + return BtDevice(id: '', name: '', type: DeviceType.omi, rssi: 0); + } return BtDevice.fromJson(jsonDecode(device)); } @@ -58,15 +61,25 @@ class SharedPreferencesUtil { set deviceIsV2(bool value) => saveBool('deviceIsV2', value); - // Double tap behavior: 0 = end conversation (default), 1 = pause/mute, 2 = star ongoing conversation - int get doubleTapAction => getInt('doubleTapAction'); + // Button behavior uses OmiButtonAction.value. + // Defaults: single = ask question, double = pause/mute, triple = end conversation. + int get singleTapAction => getInt('singleTapAction', defaultValue: OmiButtonAction.askQuestion.value); + + set singleTapAction(int value) => saveInt('singleTapAction', value); + + int get doubleTapAction => getInt('doubleTapAction', defaultValue: OmiButtonAction.pauseResume.value); set doubleTapAction(int value) => saveInt('doubleTapAction', value); + int get tripleTapAction => getInt('tripleTapAction', defaultValue: OmiButtonAction.endConversation.value); + + set tripleTapAction(int value) => saveInt('tripleTapAction', value); + // Keep backward compatibility - bool get doubleTapPausesMuting => doubleTapAction == 1; + bool get doubleTapPausesMuting => doubleTapAction == OmiButtonAction.pauseResume.value; - set doubleTapPausesMuting(bool value) => doubleTapAction = value ? 1 : 0; + set doubleTapPausesMuting(bool value) => + doubleTapAction = value ? OmiButtonAction.pauseResume.value : OmiButtonAction.endConversation.value; // Custom STT configuration CustomSttConfig get customSttConfig { diff --git a/app/lib/models/omi_button_action.dart b/app/lib/models/omi_button_action.dart new file mode 100644 index 00000000000..fc2603fee26 --- /dev/null +++ b/app/lib/models/omi_button_action.dart @@ -0,0 +1,36 @@ +enum OmiButtonAction { + endConversation(0), + pauseResume(1), + starConversation(2), + askQuestion(3), + noAction(4); + + const OmiButtonAction(this.value); + + final int value; + + static OmiButtonAction fromValue(int value, + {required OmiButtonAction fallback}) { + for (final action in values) { + if (action.value == value) return action; + } + return fallback; + } +} + +enum OmiButtonPress { + singleTap(1), + doubleTap(2), + tripleTap(6); + + const OmiButtonPress(this.state); + + final int state; + + static OmiButtonPress? fromState(int state) { + for (final press in values) { + if (press.state == state) return press; + } + return null; + } +} diff --git a/app/lib/pages/settings/device_settings.dart b/app/lib/pages/settings/device_settings.dart index 7b4171b450b..00c66a18411 100644 --- a/app/lib/pages/settings/device_settings.dart +++ b/app/lib/pages/settings/device_settings.dart @@ -6,11 +6,11 @@ import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:provider/provider.dart'; -import 'package:url_launcher/url_launcher.dart'; import 'package:omi/backend/preferences.dart'; import 'package:omi/backend/schema/bt_device/bt_device.dart'; import 'package:omi/gen/pigeon_communicator.g.dart'; +import 'package:omi/models/omi_button_action.dart'; import 'package:omi/pages/conversations/auto_sync_page.dart'; import 'package:omi/pages/conversations/sync_page.dart'; import 'package:omi/pages/home/firmware_update.dart'; @@ -18,11 +18,8 @@ import 'package:omi/pages/settings/device_diagnostics.dart'; import 'package:omi/providers/device_provider.dart'; import 'package:omi/services/devices.dart'; import 'package:omi/services/services.dart'; -import 'package:omi/utils/analytics/intercom.dart'; import 'package:omi/utils/l10n_extensions.dart'; -import 'package:omi/utils/logger.dart'; import 'package:omi/utils/other/temp.dart'; -import 'package:omi/utils/platform/platform_service.dart'; import 'package:omi/widgets/dialog.dart'; class DeviceSettings extends StatefulWidget { @@ -47,15 +44,6 @@ class _DeviceSettingsState extends State { Timer? _debounce; Timer? _micGainDebounce; - // TODO: thinh, use connection directly - Future _bleDisconnectDevice(BtDevice btDevice) async { - var connection = await ServiceManager.instance().device.ensureConnection(btDevice.id); - if (connection == null) { - return Future.value(null); - } - return await connection.disconnect(); - } - Future _bleUnpairDevice(BtDevice btDevice) async { var connection = await ServiceManager.instance().device.ensureConnection(btDevice.id); if (connection == null) { @@ -350,21 +338,69 @@ class _DeviceSettingsState extends State { ); } - String _getDoubleTapActionLabel(int action) { + String _buttonPressTitle(OmiButtonPress press) { + switch (press) { + case OmiButtonPress.singleTap: + return '1x'; + case OmiButtonPress.doubleTap: + return context.l10n.doubleTap; + case OmiButtonPress.tripleTap: + return '3x'; + } + } + + int _configuredActionValue(OmiButtonPress press) { + final preferences = SharedPreferencesUtil(); + return switch (press) { + OmiButtonPress.singleTap => preferences.singleTapAction, + OmiButtonPress.doubleTap => preferences.doubleTapAction, + OmiButtonPress.tripleTap => preferences.tripleTapAction, + }; + } + + void _setConfiguredActionValue(OmiButtonPress press, int value) { + final preferences = SharedPreferencesUtil(); + switch (press) { + case OmiButtonPress.singleTap: + preferences.singleTapAction = value; + break; + case OmiButtonPress.doubleTap: + preferences.doubleTapAction = value; + break; + case OmiButtonPress.tripleTap: + preferences.tripleTapAction = value; + break; + } + } + + String _getButtonActionLabel(OmiButtonAction action) { switch (action) { - case 0: + case OmiButtonAction.endConversation: return context.l10n.endConversation; - case 1: + case OmiButtonAction.pauseResume: return context.l10n.pauseResume; - case 2: + case OmiButtonAction.starConversation: return context.l10n.starConversation; - default: - return context.l10n.endConversation; + case OmiButtonAction.askQuestion: + return context.l10n.askOmi; + case OmiButtonAction.noAction: + return context.l10n.off; } } - void _showDoubleTapActionSheet() { - int currentAction = SharedPreferencesUtil().doubleTapAction; + String _getConfiguredActionLabel(OmiButtonPress press) { + final fallback = switch (press) { + OmiButtonPress.singleTap => OmiButtonAction.askQuestion, + OmiButtonPress.doubleTap => OmiButtonAction.pauseResume, + OmiButtonPress.tripleTap => OmiButtonAction.endConversation, + }; + return _getButtonActionLabel( + OmiButtonAction.fromValue(_configuredActionValue(press), fallback: fallback), + ); + } + + void _showButtonActionSheet(OmiButtonPress press) { + int currentAction = _configuredActionValue(press); showModalBottomSheet( context: context, @@ -384,18 +420,33 @@ class _DeviceSettingsState extends State { decoration: BoxDecoration(color: const Color(0xFF3C3C43), borderRadius: BorderRadius.circular(2)), ), Text( - context.l10n.doubleTapAction, + _buttonPressTitle(press), style: const TextStyle(color: Colors.white, fontSize: 17, fontWeight: FontWeight.w600), ), const SizedBox(height: 16), + ListTile( + title: Text( + context.l10n.askOmi, + style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w400), + ), + trailing: currentAction == OmiButtonAction.askQuestion.value + ? const Icon(Icons.check, color: Colors.white, size: 20) + : null, + onTap: () { + setState(() => _setConfiguredActionValue(press, OmiButtonAction.askQuestion.value)); + Navigator.pop(sheetContext); + }, + ), ListTile( title: Text( context.l10n.endAndProcess, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w400), ), - trailing: currentAction == 0 ? const Icon(Icons.check, color: Colors.white, size: 20) : null, + trailing: currentAction == OmiButtonAction.endConversation.value + ? const Icon(Icons.check, color: Colors.white, size: 20) + : null, onTap: () { - setState(() => SharedPreferencesUtil().doubleTapAction = 0); + setState(() => _setConfiguredActionValue(press, OmiButtonAction.endConversation.value)); Navigator.pop(sheetContext); }, ), @@ -404,9 +455,11 @@ class _DeviceSettingsState extends State { context.l10n.pauseResumeRecording, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w400), ), - trailing: currentAction == 1 ? const Icon(Icons.check, color: Colors.white, size: 20) : null, + trailing: currentAction == OmiButtonAction.pauseResume.value + ? const Icon(Icons.check, color: Colors.white, size: 20) + : null, onTap: () { - setState(() => SharedPreferencesUtil().doubleTapAction = 1); + setState(() => _setConfiguredActionValue(press, OmiButtonAction.pauseResume.value)); Navigator.pop(sheetContext); }, ), @@ -415,9 +468,24 @@ class _DeviceSettingsState extends State { context.l10n.starOngoing, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w400), ), - trailing: currentAction == 2 ? const Icon(Icons.check, color: Colors.white, size: 20) : null, + trailing: currentAction == OmiButtonAction.starConversation.value + ? const Icon(Icons.check, color: Colors.white, size: 20) + : null, + onTap: () { + setState(() => _setConfiguredActionValue(press, OmiButtonAction.starConversation.value)); + Navigator.pop(sheetContext); + }, + ), + ListTile( + title: Text( + context.l10n.off, + style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w400), + ), + trailing: currentAction == OmiButtonAction.noAction.value + ? const Icon(Icons.check, color: Colors.white, size: 20) + : null, onTap: () { - setState(() => SharedPreferencesUtil().doubleTapAction = 2); + setState(() => _setConfiguredActionValue(press, OmiButtonAction.noAction.value)); Navigator.pop(sheetContext); }, ), @@ -470,7 +538,7 @@ class _DeviceSettingsState extends State { activeTrackColor: Colors.white, inactiveTrackColor: Colors.grey.shade800, thumbColor: Colors.white, - overlayColor: Colors.white.withOpacity(0.1), + overlayColor: Colors.white.withValues(alpha: 0.1), thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 12, elevation: 2), overlayShape: const RoundSliderOverlayShape(overlayRadius: 24), trackHeight: 6, @@ -579,7 +647,7 @@ class _DeviceSettingsState extends State { activeTrackColor: Colors.white, inactiveTrackColor: Colors.grey.shade800, thumbColor: Colors.white, - overlayColor: Colors.white.withOpacity(0.1), + overlayColor: Colors.white.withValues(alpha: 0.1), thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 12, elevation: 2), overlayShape: const RoundSliderOverlayShape(overlayRadius: 24), trackHeight: 6, @@ -660,9 +728,9 @@ class _DeviceSettingsState extends State { child: Container( padding: const EdgeInsets.symmetric(vertical: 10), decoration: BoxDecoration( - color: isSelected ? Colors.white.withOpacity(0.1) : const Color(0xFF2A2A2E), + color: isSelected ? Colors.white.withValues(alpha: 0.1) : const Color(0xFF2A2A2E), borderRadius: BorderRadius.circular(10), - border: Border.all(color: isSelected ? Colors.white.withOpacity(0.5) : Colors.transparent, width: 1), + border: Border.all(color: isSelected ? Colors.white.withValues(alpha: 0.5) : Colors.transparent, width: 1), ), child: Center( child: Text( @@ -684,18 +752,29 @@ class _DeviceSettingsState extends State { } Widget _buildCustomizationSection() { - final doubleTapAction = SharedPreferencesUtil().doubleTapAction; - return Container( decoration: BoxDecoration(color: const Color(0xFF1C1C1E), borderRadius: BorderRadius.circular(20)), child: Column( children: [ - // Double Tap _buildProfileStyleItem( icon: FontAwesomeIcons.handPointer, - title: context.l10n.doubleTap, - chipValue: _getDoubleTapActionLabel(doubleTapAction), - onTap: _showDoubleTapActionSheet, + title: _buttonPressTitle(OmiButtonPress.singleTap), + chipValue: _getConfiguredActionLabel(OmiButtonPress.singleTap), + onTap: () => _showButtonActionSheet(OmiButtonPress.singleTap), + ), + const Divider(height: 1, color: Color(0xFF3C3C43)), + _buildProfileStyleItem( + icon: FontAwesomeIcons.handPointer, + title: _buttonPressTitle(OmiButtonPress.doubleTap), + chipValue: _getConfiguredActionLabel(OmiButtonPress.doubleTap), + onTap: () => _showButtonActionSheet(OmiButtonPress.doubleTap), + ), + const Divider(height: 1, color: Color(0xFF3C3C43)), + _buildProfileStyleItem( + icon: FontAwesomeIcons.handPointer, + title: _buttonPressTitle(OmiButtonPress.tripleTap), + chipValue: _getConfiguredActionLabel(OmiButtonPress.tripleTap), + onTap: () => _showButtonActionSheet(OmiButtonPress.tripleTap), ), // LED Brightness if (_isDimRatioLoaded && _hasDimmingFeature == true) ...[ @@ -731,6 +810,9 @@ class _DeviceSettingsState extends State { // Disconnect GestureDetector( onTap: () async { + final navigator = Navigator.of(context); + final messenger = ScaffoldMessenger.of(context); + final disconnectedMessage = context.l10n.deviceDisconnectedMessage; final deviceId = provider.connectedDevice?.id ?? SharedPreferencesUtil().btDevice.id; await SharedPreferencesUtil().btDeviceSet(BtDevice(id: '', name: '', type: DeviceType.omi, rssi: 0)); @@ -747,11 +829,9 @@ class _DeviceSettingsState extends State { await provider.setConnectedDevice(null); provider.updateConnectingStatus(false); PlatformManager.instance.analytics.disconnectFriendClicked(); - if (context.mounted) { - Navigator.of(context).pop(); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(context.l10n.deviceDisconnectedMessage))); + if (mounted) { + navigator.pop(); + messenger.showSnackBar(SnackBar(content: Text(disconnectedMessage))); } }, child: Padding( @@ -785,6 +865,9 @@ class _DeviceSettingsState extends State { () => Navigator.of(context).pop(), () async { Navigator.of(context).pop(); + final navigator = Navigator.of(context); + final messenger = ScaffoldMessenger.of(context); + final unpairedMessage = context.l10n.deviceUnpairedMessage; await SharedPreferencesUtil().btDeviceSet( BtDevice(id: '', name: '', type: DeviceType.omi, rssi: 0), ); @@ -795,11 +878,11 @@ class _DeviceSettingsState extends State { provider.setIsConnected(false); provider.setConnectedDevice(null); provider.updateConnectingStatus(false); - if (context.mounted) { - Navigator.of(context).pop(); - ScaffoldMessenger.of(context).showSnackBar( + if (mounted) { + navigator.pop(); + messenger.showSnackBar( SnackBar( - content: Text(context.l10n.deviceUnpairedMessage), + content: Text(unpairedMessage), duration: const Duration(seconds: 5), ), ); diff --git a/app/lib/providers/capture_provider.dart b/app/lib/providers/capture_provider.dart index 6304bf98b2c..f7123b42651 100644 --- a/app/lib/providers/capture_provider.dart +++ b/app/lib/providers/capture_provider.dart @@ -4,14 +4,12 @@ import 'dart:io'; import 'package:omi/utils/platform/platform_manager.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:collection/collection.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:flutter_provider_utilities/flutter_provider_utilities.dart'; import 'package:geolocator/geolocator.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:omi/backend/http/api/conversations.dart'; @@ -26,7 +24,7 @@ import 'package:omi/backend/schema/person.dart'; import 'package:omi/backend/schema/structured.dart'; import 'package:omi/backend/schema/transcript_segment.dart'; import 'package:omi/models/custom_stt_config.dart'; -import 'package:omi/models/stt_provider.dart'; +import 'package:omi/models/omi_button_action.dart'; import 'package:omi/providers/conversation_provider.dart'; import 'package:omi/providers/message_provider.dart'; import 'package:omi/providers/people_provider.dart'; @@ -602,6 +600,109 @@ class CaptureProvider extends ChangeNotifier _processVoiceCommandBytes(deviceId, data); } + void _toggleVoiceQuestionSession(String deviceId, String source) { + debugPrint("$source detected"); + if (_voiceCommandSession == null) { + debugPrint("Starting voice question session ($source)"); + if (OmiVoicePlaybackService.instance.isSpeaking) { + OmiVoicePlaybackService.instance.interrupt(); + } + _voiceCommandSession = DateTime.now(); + _commandBytes = []; + _voiceSessionStartedByLegacyLongPress = false; + _startVoiceCommandTimeout(deviceId); + _playSpeakerHaptic(deviceId, 1); + } else if (!_voiceSessionStartedByLegacyLongPress) { + debugPrint("Ending voice question session ($source)"); + _endVoiceCommandSession(deviceId); + } + } + + OmiButtonAction _buttonActionForPress(OmiButtonPress press) { + final preferences = SharedPreferencesUtil(); + return switch (press) { + OmiButtonPress.singleTap => OmiButtonAction.fromValue( + preferences.singleTapAction, + fallback: OmiButtonAction.askQuestion, + ), + OmiButtonPress.doubleTap => OmiButtonAction.fromValue( + preferences.doubleTapAction, + fallback: OmiButtonAction.pauseResume, + ), + OmiButtonPress.tripleTap => OmiButtonAction.fromValue( + preferences.tripleTapAction, + fallback: OmiButtonAction.endConversation, + ), + }; + } + + String _buttonPressSource(OmiButtonPress press) { + return switch (press) { + OmiButtonPress.singleTap => 'single tap', + OmiButtonPress.doubleTap => 'double tap', + OmiButtonPress.tripleTap => 'triple tap', + }; + } + + Future _runButtonAction({ + required OmiButtonAction action, + required String deviceId, + required String source, + }) async { + if (_isProcessingButtonEvent) { + Logger.debug("$source: already processing, ignoring"); + return; + } + + switch (action) { + case OmiButtonAction.askQuestion: + _toggleVoiceQuestionSession(deviceId, source); + return; + case OmiButtonAction.pauseResume: + Logger.debug("$source: toggling pause/mute"); + _isProcessingButtonEvent = true; + try { + if (_isPaused) { + PlatformManager.instance.analytics.omiDoubleTap(feature: 'unmute'); + await resumeDeviceRecording(); + } else { + PlatformManager.instance.analytics.omiDoubleTap(feature: 'mute'); + await pauseDeviceRecording(); + } + } catch (e) { + Logger.debug("Error toggling device recording from $source: $e"); + } finally { + _isProcessingButtonEvent = false; + } + return; + case OmiButtonAction.starConversation: + Logger.debug("$source: toggling conversation star"); + if (!_starOngoingConversation) { + markConversationForStarring(); + PlatformManager.instance.analytics.omiDoubleTap(feature: 'star_conversation'); + HapticFeedback.mediumImpact(); + } else { + unmarkConversationForStarring(); + PlatformManager.instance.analytics.omiDoubleTap(feature: 'unstar_conversation'); + HapticFeedback.lightImpact(); + } + return; + case OmiButtonAction.endConversation: + Logger.debug("$source: processing conversation"); + _isProcessingButtonEvent = true; + try { + PlatformManager.instance.analytics.omiDoubleTap(feature: 'process_conversation'); + await forceProcessingCurrentConversation(); + } finally { + _isProcessingButtonEvent = false; + } + return; + case OmiButtonAction.noAction: + Logger.debug("$source: no action configured"); + return; + } + } + Future streamButton(String deviceId) async { Logger.debug('streamButton in capture_provider'); _bleButtonStream?.cancel(); @@ -615,84 +716,16 @@ class CaptureProvider extends ChangeNotifier ).getUint32(0); Logger.debug("device button $buttonState"); - // double tap - if (buttonState == 2) { - Logger.debug("Double tap detected"); - - // Guard: ignore if already processing a button event - if (_isProcessingButtonEvent) { - Logger.debug("Double tap: already processing, ignoring"); - return; - } - - int doubleTapAction = SharedPreferencesUtil().doubleTapAction; - - if (doubleTapAction == 1) { - // Pause/resume recording - Logger.debug("Double tap: toggling pause/mute"); - _isProcessingButtonEvent = true; - if (_isPaused) { - PlatformManager.instance.analytics.omiDoubleTap(feature: 'unmute'); - resumeDeviceRecording().then((_) { - _isProcessingButtonEvent = false; - }).catchError((e) { - Logger.debug("Error resuming device recording: $e"); - _isProcessingButtonEvent = false; - }); - } else { - PlatformManager.instance.analytics.omiDoubleTap(feature: 'mute'); - pauseDeviceRecording().then((_) { - _isProcessingButtonEvent = false; - }).catchError((e) { - Logger.debug("Error pausing device recording: $e"); - _isProcessingButtonEvent = false; - }); - } - } else if (doubleTapAction == 2) { - // Star ongoing conversation (doesn't end it) - Logger.debug("Double tap: marking conversation for starring"); - if (!_starOngoingConversation) { - markConversationForStarring(); - PlatformManager.instance.analytics.omiDoubleTap(feature: 'star_conversation'); - // Haptic feedback to confirm - HapticFeedback.mediumImpact(); - } else { - // Toggle off if already marked - unmarkConversationForStarring(); - PlatformManager.instance.analytics.omiDoubleTap(feature: 'unstar_conversation'); - HapticFeedback.lightImpact(); - } - } else { - // End conversation and process (default) - Logger.debug("Double tap: processing conversation"); - PlatformManager.instance.analytics.omiDoubleTap(feature: 'process_conversation'); - forceProcessingCurrentConversation(); - } - return; - } - - // Single tap (buttonState == 1) - toggle voice question mode - // Tap once to start, tap again to end - if (buttonState == 1) { - debugPrint("Single tap detected"); - if (_voiceCommandSession == null) { - // Start voice question session (new toggle mode) - debugPrint("Starting voice question session (toggle mode)"); - // Cut off any in-flight voice playback from a prior reply so the - // new recording starts clean. - if (OmiVoicePlaybackService.instance.isSpeaking) { - OmiVoicePlaybackService.instance.interrupt(); - } - _voiceCommandSession = DateTime.now(); - _commandBytes = []; - _voiceSessionStartedByLegacyLongPress = false; // New toggle mode - _startVoiceCommandTimeout(deviceId); - _playSpeakerHaptic(deviceId, 1); - } else if (!_voiceSessionStartedByLegacyLongPress) { - // Only end on second tap if session was started by toggle mode (not legacy) - debugPrint("Ending voice question session (toggle mode)"); - _endVoiceCommandSession(deviceId); - } + final press = OmiButtonPress.fromState(buttonState); + if (press != null) { + final action = _buttonActionForPress(press); + unawaited( + _runButtonAction( + action: action, + deviceId: deviceId, + source: _buttonPressSource(press), + ), + ); return; } diff --git a/app/test/unit/omi_button_action_test.dart b/app/test/unit/omi_button_action_test.dart new file mode 100644 index 00000000000..43ba2be3b58 --- /dev/null +++ b/app/test/unit/omi_button_action_test.dart @@ -0,0 +1,49 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:omi/backend/preferences.dart'; +import 'package:omi/models/omi_button_action.dart'; + +void main() { + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + }); + + group('Omi button actions', () { + setUp(() async { + SharedPreferences.setMockInitialValues({}); + await SharedPreferencesUtil.init(); + }); + + test('keeps stable stored values', () { + expect(OmiButtonAction.endConversation.value, 0); + expect(OmiButtonAction.pauseResume.value, 1); + expect(OmiButtonAction.starConversation.value, 2); + expect(OmiButtonAction.askQuestion.value, 3); + expect(OmiButtonAction.noAction.value, 4); + }); + + test('defaults match expected button layout', () { + final preferences = SharedPreferencesUtil(); + + expect(preferences.singleTapAction, OmiButtonAction.askQuestion.value); + expect(preferences.doubleTapAction, OmiButtonAction.pauseResume.value); + expect( + preferences.tripleTapAction, OmiButtonAction.endConversation.value); + }); + + test('maps BLE button states to configurable press types', () { + expect(OmiButtonPress.fromState(1), OmiButtonPress.singleTap); + expect(OmiButtonPress.fromState(2), OmiButtonPress.doubleTap); + expect(OmiButtonPress.fromState(6), OmiButtonPress.tripleTap); + expect(OmiButtonPress.fromState(3), isNull); + }); + + test('falls back when stored action value is unknown', () { + expect( + OmiButtonAction.fromValue(999, fallback: OmiButtonAction.pauseResume), + OmiButtonAction.pauseResume, + ); + }); + }); +} diff --git a/omi/firmware/devkit/src/button.c b/omi/firmware/devkit/src/button.c index cf299af6d28..62d60083617 100644 --- a/omi/firmware/devkit/src/button.c +++ b/omi/firmware/devkit/src/button.c @@ -89,6 +89,7 @@ K_WORK_DELAYABLE_DEFINE(button_work, check_button_level); #define LONG_TAP 3 #define BUTTON_PRESS 4 #define BUTTON_RELEASE 5 +#define TRIPLE_TAP 6 // 4 is button down, 5 is button up static FSM_STATE_T current_button_state = IDLE; @@ -103,54 +104,45 @@ static void reset_count() inc_count_0 = 0; inc_count_1 = 0; } -static inline void notify_press() + +static inline void notify_button_state(int state, const char *label) { - final_button_state[0] = BUTTON_PRESS; - LOG_INF("Button pressed"); + final_button_state[0] = state; + LOG_INF("Button %s", label); struct bt_conn *conn = get_current_connection(); if (conn != NULL) { bt_gatt_notify(conn, &button_service.attrs[1], &final_button_state, sizeof(final_button_state)); } } +static inline void notify_press() +{ + notify_button_state(BUTTON_PRESS, "pressed"); +} + static inline void notify_unpress() { - final_button_state[0] = BUTTON_RELEASE; - LOG_INF("Button released"); - struct bt_conn *conn = get_current_connection(); - if (conn != NULL) { - bt_gatt_notify(conn, &button_service.attrs[1], &final_button_state, sizeof(final_button_state)); - } + notify_button_state(BUTTON_RELEASE, "released"); } static inline void notify_tap() { - final_button_state[0] = SINGLE_TAP; - LOG_INF("Button single tap"); - struct bt_conn *conn = get_current_connection(); - if (conn != NULL) { - bt_gatt_notify(conn, &button_service.attrs[1], &final_button_state, sizeof(final_button_state)); - } + notify_button_state(SINGLE_TAP, "single tap"); } static inline void notify_double_tap() { - final_button_state[0] = DOUBLE_TAP; // button press - LOG_INF("Button double tap"); - struct bt_conn *conn = get_current_connection(); - if (conn != NULL) { - bt_gatt_notify(conn, &button_service.attrs[1], &final_button_state, sizeof(final_button_state)); - } + notify_button_state(DOUBLE_TAP, "double tap"); +} + +static inline void notify_triple_tap() +{ + notify_button_state(TRIPLE_TAP, "triple tap"); } static inline void notify_long_tap() { - final_button_state[0] = LONG_TAP; // button press - LOG_INF("Button long tap"); - struct bt_conn *conn = get_current_connection(); - if (conn != NULL) { - bt_gatt_notify(conn, &button_service.attrs[1], &final_button_state, sizeof(final_button_state)); - } + notify_button_state(LONG_TAP, "long tap"); } #define BUTTON_PRESSED 1 @@ -164,6 +156,7 @@ typedef enum { BUTTON_EVENT_NONE, BUTTON_EVENT_SINGLE_TAP, BUTTON_EVENT_DOUBLE_TAP, + BUTTON_EVENT_TRIPLE_TAP, BUTTON_EVENT_LONG_PRESS, BUTTON_EVENT_RELEASE } ButtonEvent; @@ -173,9 +166,18 @@ static uint32_t btn_press_start_time; static uint32_t btn_release_time; static uint32_t btn_last_tap_time; static bool btn_is_pressed; +static uint8_t btn_tap_count; static u_int8_t btn_last_event = BUTTON_EVENT_NONE; +static void reset_tap_tracking() +{ + btn_tap_count = 0; + btn_press_start_time = 0; + btn_release_time = 0; + btn_last_tap_time = 0; +} + void check_button_level(struct k_work *work_item) { current_time = current_time + 1; @@ -192,28 +194,26 @@ void check_button_level(struct k_work *work_item) btn_is_pressed = false; btn_release_time = current_time; - // Check for double tap uint32_t press_duration = (btn_release_time - btn_press_start_time) * BUTTON_CHECK_INTERVAL; if (press_duration < TAP_THRESHOLD) { - if (btn_last_tap_time > 0 && - (current_time - btn_last_tap_time) * BUTTON_CHECK_INTERVAL < DOUBLE_TAP_WINDOW) { - event = BUTTON_EVENT_DOUBLE_TAP; - btn_last_tap_time = 0; // Reset double-tap / single-tap detection - } else { - btn_last_tap_time = current_time; + if (btn_tap_count < 3) { + btn_tap_count++; } + btn_last_tap_time = current_time; + } else { + event = BUTTON_EVENT_RELEASE; } } - // Check for single tap - if (btn_state == BUTTON_RELEASED && !btn_is_pressed) { - uint32_t press_duration = (btn_release_time - btn_press_start_time) * BUTTON_CHECK_INTERVAL; - if (press_duration < TAP_THRESHOLD && btn_last_tap_time > 0 && - (current_time - btn_press_start_time) * BUTTON_CHECK_INTERVAL > TAP_THRESHOLD) { + // Resolve tap sequences only after the multi-tap window passes. + if (event == BUTTON_EVENT_NONE && btn_state == BUTTON_RELEASED && !btn_is_pressed && btn_tap_count > 0 && + (current_time - btn_last_tap_time) * BUTTON_CHECK_INTERVAL > DOUBLE_TAP_WINDOW) { + if (btn_tap_count == 1) { event = BUTTON_EVENT_SINGLE_TAP; - btn_last_tap_time = 0; - } else if ((current_time - btn_press_start_time) * BUTTON_CHECK_INTERVAL > TAP_THRESHOLD) { - event = BUTTON_EVENT_RELEASE; + } else if (btn_tap_count == 2) { + event = BUTTON_EVENT_DOUBLE_TAP; + } else { + event = BUTTON_EVENT_TRIPLE_TAP; } } @@ -227,6 +227,7 @@ void check_button_level(struct k_work *work_item) LOG_PRINTK("single tap detected\n"); btn_last_event = event; notify_tap(); + reset_tap_tracking(); } // Double tap @@ -234,12 +235,22 @@ void check_button_level(struct k_work *work_item) LOG_PRINTK("double tap detected\n"); btn_last_event = event; notify_double_tap(); + reset_tap_tracking(); + } + + // Triple tap + if (event == BUTTON_EVENT_TRIPLE_TAP) { + LOG_PRINTK("triple tap detected\n"); + btn_last_event = event; + notify_triple_tap(); + reset_tap_tracking(); } // Long press, one time event if (event == BUTTON_EVENT_LONG_PRESS && btn_last_event != BUTTON_EVENT_LONG_PRESS) { LOG_PRINTK("long press detected\n"); btn_last_event = event; + reset_tap_tracking(); // Enter the low power mode is_off = true; @@ -255,9 +266,7 @@ void check_button_level(struct k_work *work_item) // Reset current_time = 0; - btn_press_start_time = 0; - btn_release_time = 0; - btn_last_tap_time = 0; + reset_tap_tracking(); } if (event == BUTTON_EVENT_RELEASE) { current_button_state = GRACE; diff --git a/omi/firmware/omi/src/lib/core/button.c b/omi/firmware/omi/src/lib/core/button.c index 9e6af77d806..45384057b82 100644 --- a/omi/firmware/omi/src/lib/core/button.c +++ b/omi/firmware/omi/src/lib/core/button.c @@ -82,6 +82,7 @@ K_WORK_DELAYABLE_DEFINE(button_work, check_button_level); #define LONG_TAP 3 #define BUTTON_PRESS 4 #define BUTTON_RELEASE 5 +#define TRIPLE_TAP 6 // 4 is button down, 5 is button up static FSM_STATE_T current_button_state = IDLE; @@ -96,54 +97,45 @@ static void reset_count() inc_count_0 = 0; inc_count_1 = 0; } -static inline void notify_press() + +static inline void notify_button_state(int state, const char *label) { - final_button_state[0] = BUTTON_PRESS; - LOG_INF("Button pressed"); + final_button_state[0] = state; + LOG_INF("Button %s", label); struct bt_conn *conn = get_current_connection(); if (conn != NULL) { bt_gatt_notify(conn, &button_service.attrs[1], &final_button_state, sizeof(final_button_state)); } } +static inline void notify_press() +{ + notify_button_state(BUTTON_PRESS, "pressed"); +} + static inline void notify_unpress() { - final_button_state[0] = BUTTON_RELEASE; - LOG_INF("Button released"); - struct bt_conn *conn = get_current_connection(); - if (conn != NULL) { - bt_gatt_notify(conn, &button_service.attrs[1], &final_button_state, sizeof(final_button_state)); - } + notify_button_state(BUTTON_RELEASE, "released"); } static inline void notify_tap() { - final_button_state[0] = SINGLE_TAP; - LOG_INF("Button single tap"); - struct bt_conn *conn = get_current_connection(); - if (conn != NULL) { - bt_gatt_notify(conn, &button_service.attrs[1], &final_button_state, sizeof(final_button_state)); - } + notify_button_state(SINGLE_TAP, "single tap"); } static inline void notify_double_tap() { - final_button_state[0] = DOUBLE_TAP; // button press - LOG_INF("Button double tap"); - struct bt_conn *conn = get_current_connection(); - if (conn != NULL) { - bt_gatt_notify(conn, &button_service.attrs[1], &final_button_state, sizeof(final_button_state)); - } + notify_button_state(DOUBLE_TAP, "double tap"); +} + +static inline void notify_triple_tap() +{ + notify_button_state(TRIPLE_TAP, "triple tap"); } static inline void notify_long_tap() { - final_button_state[0] = LONG_TAP; // button press - LOG_INF("Button long tap"); - struct bt_conn *conn = get_current_connection(); - if (conn != NULL) { - bt_gatt_notify(conn, &button_service.attrs[1], &final_button_state, sizeof(final_button_state)); - } + notify_button_state(LONG_TAP, "long tap"); } #define BUTTON_PRESSED 1 @@ -157,6 +149,7 @@ typedef enum { BUTTON_EVENT_NONE, BUTTON_EVENT_SINGLE_TAP, BUTTON_EVENT_DOUBLE_TAP, + BUTTON_EVENT_TRIPLE_TAP, BUTTON_EVENT_LONG_PRESS, BUTTON_EVENT_RELEASE } ButtonEvent; @@ -166,9 +159,18 @@ static uint32_t btn_press_start_time; static uint32_t btn_release_time; static uint32_t btn_last_tap_time; static bool btn_is_pressed; +static uint8_t btn_tap_count; static u_int8_t btn_last_event = BUTTON_EVENT_NONE; +static void reset_tap_tracking() +{ + btn_tap_count = 0; + btn_press_start_time = 0; + btn_release_time = 0; + btn_last_tap_time = 0; +} + void check_button_level(struct k_work *work_item) { current_time = current_time + 1; @@ -185,28 +187,26 @@ void check_button_level(struct k_work *work_item) btn_is_pressed = false; btn_release_time = current_time; - // Check for double tap uint32_t press_duration = (btn_release_time - btn_press_start_time) * BUTTON_CHECK_INTERVAL; if (press_duration < TAP_THRESHOLD) { - if (btn_last_tap_time > 0 && - (current_time - btn_last_tap_time) * BUTTON_CHECK_INTERVAL < DOUBLE_TAP_WINDOW) { - event = BUTTON_EVENT_DOUBLE_TAP; - btn_last_tap_time = 0; // Reset double-tap / single-tap detection - } else { - btn_last_tap_time = current_time; + if (btn_tap_count < 3) { + btn_tap_count++; } + btn_last_tap_time = current_time; + } else { + event = BUTTON_EVENT_RELEASE; } } - // Check for single tap - if (btn_state == BUTTON_RELEASED && !btn_is_pressed) { - uint32_t press_duration = (btn_release_time - btn_press_start_time) * BUTTON_CHECK_INTERVAL; - if (press_duration < TAP_THRESHOLD && btn_last_tap_time > 0 && - (current_time - btn_press_start_time) * BUTTON_CHECK_INTERVAL > TAP_THRESHOLD) { + // Resolve tap sequences only after the multi-tap window passes. + if (event == BUTTON_EVENT_NONE && btn_state == BUTTON_RELEASED && !btn_is_pressed && btn_tap_count > 0 && + (current_time - btn_last_tap_time) * BUTTON_CHECK_INTERVAL > DOUBLE_TAP_WINDOW) { + if (btn_tap_count == 1) { event = BUTTON_EVENT_SINGLE_TAP; - btn_last_tap_time = 0; - } else if ((current_time - btn_press_start_time) * BUTTON_CHECK_INTERVAL > TAP_THRESHOLD) { - event = BUTTON_EVENT_RELEASE; + } else if (btn_tap_count == 2) { + event = BUTTON_EVENT_DOUBLE_TAP; + } else { + event = BUTTON_EVENT_TRIPLE_TAP; } } @@ -219,8 +219,8 @@ void check_button_level(struct k_work *work_item) if (event == BUTTON_EVENT_SINGLE_TAP) { LOG_INF("single tap detected\n"); btn_last_event = event; - notify_tap(); + reset_tap_tracking(); } // Double tap @@ -228,12 +228,22 @@ void check_button_level(struct k_work *work_item) LOG_INF("double tap detected\n"); btn_last_event = event; notify_double_tap(); + reset_tap_tracking(); + } + + // Triple tap + if (event == BUTTON_EVENT_TRIPLE_TAP) { + LOG_INF("triple tap detected\n"); + btn_last_event = event; + notify_triple_tap(); + reset_tap_tracking(); } // Long press, one time event if (event == BUTTON_EVENT_LONG_PRESS && btn_last_event != BUTTON_EVENT_LONG_PRESS) { LOG_INF("long press detected\n"); btn_last_event = event; + reset_tap_tracking(); turnoff_all(); } @@ -245,9 +255,7 @@ void check_button_level(struct k_work *work_item) // Reset current_time = 0; - btn_press_start_time = 0; - btn_release_time = 0; - btn_last_tap_time = 0; + reset_tap_tracking(); } if (event == BUTTON_EVENT_RELEASE) { current_button_state = GRACE; From d4ce44f85b466335bcd2ce51087168b3de357e25 Mon Sep 17 00:00:00 2001 From: Rogier Muller Date: Sat, 16 May 2026 16:34:25 +0200 Subject: [PATCH 2/2] Address button action review feedback --- app/lib/l10n/app_en.arb | 8 ++++++++ app/lib/l10n/app_localizations.dart | 12 ++++++++++++ app/lib/l10n/app_localizations_ar.dart | 6 ++++++ app/lib/l10n/app_localizations_be.dart | 6 ++++++ app/lib/l10n/app_localizations_bg.dart | 6 ++++++ app/lib/l10n/app_localizations_bn.dart | 6 ++++++ app/lib/l10n/app_localizations_bs.dart | 6 ++++++ app/lib/l10n/app_localizations_ca.dart | 6 ++++++ app/lib/l10n/app_localizations_cs.dart | 6 ++++++ app/lib/l10n/app_localizations_da.dart | 6 ++++++ app/lib/l10n/app_localizations_de.dart | 6 ++++++ app/lib/l10n/app_localizations_el.dart | 6 ++++++ app/lib/l10n/app_localizations_en.dart | 6 ++++++ app/lib/l10n/app_localizations_es.dart | 6 ++++++ app/lib/l10n/app_localizations_et.dart | 6 ++++++ app/lib/l10n/app_localizations_fa.dart | 6 ++++++ app/lib/l10n/app_localizations_fi.dart | 6 ++++++ app/lib/l10n/app_localizations_fr.dart | 6 ++++++ app/lib/l10n/app_localizations_he.dart | 6 ++++++ app/lib/l10n/app_localizations_hi.dart | 6 ++++++ app/lib/l10n/app_localizations_hr.dart | 6 ++++++ app/lib/l10n/app_localizations_hu.dart | 6 ++++++ app/lib/l10n/app_localizations_id.dart | 6 ++++++ app/lib/l10n/app_localizations_it.dart | 6 ++++++ app/lib/l10n/app_localizations_ja.dart | 6 ++++++ app/lib/l10n/app_localizations_kn.dart | 6 ++++++ app/lib/l10n/app_localizations_ko.dart | 6 ++++++ app/lib/l10n/app_localizations_lt.dart | 6 ++++++ app/lib/l10n/app_localizations_lv.dart | 6 ++++++ app/lib/l10n/app_localizations_mk.dart | 6 ++++++ app/lib/l10n/app_localizations_mr.dart | 6 ++++++ app/lib/l10n/app_localizations_ms.dart | 6 ++++++ app/lib/l10n/app_localizations_nl.dart | 6 ++++++ app/lib/l10n/app_localizations_no.dart | 6 ++++++ app/lib/l10n/app_localizations_pl.dart | 6 ++++++ app/lib/l10n/app_localizations_pt.dart | 6 ++++++ app/lib/l10n/app_localizations_ro.dart | 6 ++++++ app/lib/l10n/app_localizations_ru.dart | 6 ++++++ app/lib/l10n/app_localizations_sk.dart | 6 ++++++ app/lib/l10n/app_localizations_sl.dart | 6 ++++++ app/lib/l10n/app_localizations_sr.dart | 6 ++++++ app/lib/l10n/app_localizations_sv.dart | 6 ++++++ app/lib/l10n/app_localizations_ta.dart | 6 ++++++ app/lib/l10n/app_localizations_te.dart | 6 ++++++ app/lib/l10n/app_localizations_th.dart | 6 ++++++ app/lib/l10n/app_localizations_tl.dart | 6 ++++++ app/lib/l10n/app_localizations_tr.dart | 6 ++++++ app/lib/l10n/app_localizations_uk.dart | 6 ++++++ app/lib/l10n/app_localizations_ur.dart | 6 ++++++ app/lib/l10n/app_localizations_vi.dart | 6 ++++++ app/lib/l10n/app_localizations_zh.dart | 6 ++++++ app/lib/pages/settings/device_settings.dart | 4 ++-- app/lib/providers/capture_provider.dart | 19 ++++++++++++++----- .../utils/analytics/analytics_manager.dart | 15 +++++++++++++++ 54 files changed, 345 insertions(+), 7 deletions(-) diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index dc98f4c81ce..06d8167d05f 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -667,6 +667,14 @@ "@doubleTap": { "description": "Double tap action label" }, + "singleTap": "Single Tap", + "@singleTap": { + "description": "Single tap action label" + }, + "tripleTap": "Triple Tap", + "@tripleTap": { + "description": "Triple tap action label" + }, "ledBrightness": "LED Brightness", "@ledBrightness": { "description": "LED brightness setting" diff --git a/app/lib/l10n/app_localizations.dart b/app/lib/l10n/app_localizations.dart index 85b784d1306..d228ded024b 100644 --- a/app/lib/l10n/app_localizations.dart +++ b/app/lib/l10n/app_localizations.dart @@ -1155,6 +1155,18 @@ abstract class AppLocalizations { /// **'Double Tap'** String get doubleTap; + /// Single tap action label + /// + /// In en, this message translates to: + /// **'Single Tap'** + String get singleTap; + + /// Triple tap action label + /// + /// In en, this message translates to: + /// **'Triple Tap'** + String get tripleTap; + /// LED brightness setting /// /// In en, this message translates to: diff --git a/app/lib/l10n/app_localizations_ar.dart b/app/lib/l10n/app_localizations_ar.dart index 179682ac2a1..9fdc8ac25f5 100644 --- a/app/lib/l10n/app_localizations_ar.dart +++ b/app/lib/l10n/app_localizations_ar.dart @@ -504,6 +504,12 @@ class AppLocalizationsAr extends AppLocalizations { @override String get doubleTap => 'نقرة مزدوجة'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'سطوع LED'; diff --git a/app/lib/l10n/app_localizations_be.dart b/app/lib/l10n/app_localizations_be.dart index d8ee87c7a6f..e1f830528e1 100644 --- a/app/lib/l10n/app_localizations_be.dart +++ b/app/lib/l10n/app_localizations_be.dart @@ -508,6 +508,12 @@ class AppLocalizationsBe extends AppLocalizations { @override String get doubleTap => 'Двайны дотык'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Яркасць LED'; diff --git a/app/lib/l10n/app_localizations_bg.dart b/app/lib/l10n/app_localizations_bg.dart index e2be8904879..eb78023b2f9 100644 --- a/app/lib/l10n/app_localizations_bg.dart +++ b/app/lib/l10n/app_localizations_bg.dart @@ -510,6 +510,12 @@ class AppLocalizationsBg extends AppLocalizations { @override String get doubleTap => 'Двойно докосване'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Яркост на LED'; diff --git a/app/lib/l10n/app_localizations_bn.dart b/app/lib/l10n/app_localizations_bn.dart index 72042d72716..4dd8b516477 100644 --- a/app/lib/l10n/app_localizations_bn.dart +++ b/app/lib/l10n/app_localizations_bn.dart @@ -508,6 +508,12 @@ class AppLocalizationsBn extends AppLocalizations { @override String get doubleTap => 'দ্বিগুণ ট্যাপ'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED উজ্জ্বলতা'; diff --git a/app/lib/l10n/app_localizations_bs.dart b/app/lib/l10n/app_localizations_bs.dart index d75ac1fe0e9..0ad6349b200 100644 --- a/app/lib/l10n/app_localizations_bs.dart +++ b/app/lib/l10n/app_localizations_bs.dart @@ -509,6 +509,12 @@ class AppLocalizationsBs extends AppLocalizations { @override String get doubleTap => 'Dupli dodir'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Svetlina LED-a'; diff --git a/app/lib/l10n/app_localizations_ca.dart b/app/lib/l10n/app_localizations_ca.dart index f956cdc2dae..867d20f98da 100644 --- a/app/lib/l10n/app_localizations_ca.dart +++ b/app/lib/l10n/app_localizations_ca.dart @@ -510,6 +510,12 @@ class AppLocalizationsCa extends AppLocalizations { @override String get doubleTap => 'Doble toc'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Brillantor LED'; diff --git a/app/lib/l10n/app_localizations_cs.dart b/app/lib/l10n/app_localizations_cs.dart index d64c0ada1a2..f44c633f0cf 100644 --- a/app/lib/l10n/app_localizations_cs.dart +++ b/app/lib/l10n/app_localizations_cs.dart @@ -509,6 +509,12 @@ class AppLocalizationsCs extends AppLocalizations { @override String get doubleTap => 'Dvojité klepnutí'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Jas LED'; diff --git a/app/lib/l10n/app_localizations_da.dart b/app/lib/l10n/app_localizations_da.dart index 852e4cc36b7..8f4f8fb1340 100644 --- a/app/lib/l10n/app_localizations_da.dart +++ b/app/lib/l10n/app_localizations_da.dart @@ -509,6 +509,12 @@ class AppLocalizationsDa extends AppLocalizations { @override String get doubleTap => 'Dobbelttryk'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED-lysstyrke'; diff --git a/app/lib/l10n/app_localizations_de.dart b/app/lib/l10n/app_localizations_de.dart index 954d4f64106..59c73ca1bba 100644 --- a/app/lib/l10n/app_localizations_de.dart +++ b/app/lib/l10n/app_localizations_de.dart @@ -513,6 +513,12 @@ class AppLocalizationsDe extends AppLocalizations { @override String get doubleTap => 'Doppeltippen'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED-Helligkeit'; diff --git a/app/lib/l10n/app_localizations_el.dart b/app/lib/l10n/app_localizations_el.dart index 7b505ed58a7..5d69887e72d 100644 --- a/app/lib/l10n/app_localizations_el.dart +++ b/app/lib/l10n/app_localizations_el.dart @@ -512,6 +512,12 @@ class AppLocalizationsEl extends AppLocalizations { @override String get doubleTap => 'Διπλό Πάτημα'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Φωτεινότητα LED'; diff --git a/app/lib/l10n/app_localizations_en.dart b/app/lib/l10n/app_localizations_en.dart index 2389253bd1c..eb9cbb1b7e8 100644 --- a/app/lib/l10n/app_localizations_en.dart +++ b/app/lib/l10n/app_localizations_en.dart @@ -507,6 +507,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String get doubleTap => 'Double Tap'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED Brightness'; diff --git a/app/lib/l10n/app_localizations_es.dart b/app/lib/l10n/app_localizations_es.dart index cc35f7c070c..bdb814dfbd2 100644 --- a/app/lib/l10n/app_localizations_es.dart +++ b/app/lib/l10n/app_localizations_es.dart @@ -508,6 +508,12 @@ class AppLocalizationsEs extends AppLocalizations { @override String get doubleTap => 'Doble toque'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Brillo LED'; diff --git a/app/lib/l10n/app_localizations_et.dart b/app/lib/l10n/app_localizations_et.dart index 198b56bd003..753348864f1 100644 --- a/app/lib/l10n/app_localizations_et.dart +++ b/app/lib/l10n/app_localizations_et.dart @@ -510,6 +510,12 @@ class AppLocalizationsEt extends AppLocalizations { @override String get doubleTap => 'Topeltpuudutus'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED heledus'; diff --git a/app/lib/l10n/app_localizations_fa.dart b/app/lib/l10n/app_localizations_fa.dart index faf1f538a1b..e893a01bc27 100644 --- a/app/lib/l10n/app_localizations_fa.dart +++ b/app/lib/l10n/app_localizations_fa.dart @@ -509,6 +509,12 @@ class AppLocalizationsFa extends AppLocalizations { @override String get doubleTap => 'دو ضربه'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'روشنایی LED'; diff --git a/app/lib/l10n/app_localizations_fi.dart b/app/lib/l10n/app_localizations_fi.dart index adbbbedab78..e1ace8932e7 100644 --- a/app/lib/l10n/app_localizations_fi.dart +++ b/app/lib/l10n/app_localizations_fi.dart @@ -507,6 +507,12 @@ class AppLocalizationsFi extends AppLocalizations { @override String get doubleTap => 'Kaksoisnapautus'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED-kirkkaus'; diff --git a/app/lib/l10n/app_localizations_fr.dart b/app/lib/l10n/app_localizations_fr.dart index 130bdf2a396..2aa643766cd 100644 --- a/app/lib/l10n/app_localizations_fr.dart +++ b/app/lib/l10n/app_localizations_fr.dart @@ -511,6 +511,12 @@ class AppLocalizationsFr extends AppLocalizations { @override String get doubleTap => 'Double appui'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Luminosité LED'; diff --git a/app/lib/l10n/app_localizations_he.dart b/app/lib/l10n/app_localizations_he.dart index 92482906b67..8a6ffbe762a 100644 --- a/app/lib/l10n/app_localizations_he.dart +++ b/app/lib/l10n/app_localizations_he.dart @@ -505,6 +505,12 @@ class AppLocalizationsHe extends AppLocalizations { @override String get doubleTap => 'לחץ כפול'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'בהיקות LED'; diff --git a/app/lib/l10n/app_localizations_hi.dart b/app/lib/l10n/app_localizations_hi.dart index 24e7e90d4d3..044c12a6d24 100644 --- a/app/lib/l10n/app_localizations_hi.dart +++ b/app/lib/l10n/app_localizations_hi.dart @@ -506,6 +506,12 @@ class AppLocalizationsHi extends AppLocalizations { @override String get doubleTap => 'डबल टैप'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED चमक'; diff --git a/app/lib/l10n/app_localizations_hr.dart b/app/lib/l10n/app_localizations_hr.dart index e3eab019927..4845e83eca3 100644 --- a/app/lib/l10n/app_localizations_hr.dart +++ b/app/lib/l10n/app_localizations_hr.dart @@ -509,6 +509,12 @@ class AppLocalizationsHr extends AppLocalizations { @override String get doubleTap => 'Dvostruki dodir'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Svjetlina LED-a'; diff --git a/app/lib/l10n/app_localizations_hu.dart b/app/lib/l10n/app_localizations_hu.dart index c12c1fd8df3..7852d2e4d22 100644 --- a/app/lib/l10n/app_localizations_hu.dart +++ b/app/lib/l10n/app_localizations_hu.dart @@ -511,6 +511,12 @@ class AppLocalizationsHu extends AppLocalizations { @override String get doubleTap => 'Dupla érintés'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED fényerő'; diff --git a/app/lib/l10n/app_localizations_id.dart b/app/lib/l10n/app_localizations_id.dart index 7dc17ff8773..45f3d405814 100644 --- a/app/lib/l10n/app_localizations_id.dart +++ b/app/lib/l10n/app_localizations_id.dart @@ -508,6 +508,12 @@ class AppLocalizationsId extends AppLocalizations { @override String get doubleTap => 'Ketuk Ganda'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Kecerahan LED'; diff --git a/app/lib/l10n/app_localizations_it.dart b/app/lib/l10n/app_localizations_it.dart index 2bfd959a6ac..4533c8e8088 100644 --- a/app/lib/l10n/app_localizations_it.dart +++ b/app/lib/l10n/app_localizations_it.dart @@ -510,6 +510,12 @@ class AppLocalizationsIt extends AppLocalizations { @override String get doubleTap => 'Doppio Tocco'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Luminosità LED'; diff --git a/app/lib/l10n/app_localizations_ja.dart b/app/lib/l10n/app_localizations_ja.dart index 88342c66fc2..fda50cd21d4 100644 --- a/app/lib/l10n/app_localizations_ja.dart +++ b/app/lib/l10n/app_localizations_ja.dart @@ -499,6 +499,12 @@ class AppLocalizationsJa extends AppLocalizations { @override String get doubleTap => 'ダブルタップ'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED明るさ'; diff --git a/app/lib/l10n/app_localizations_kn.dart b/app/lib/l10n/app_localizations_kn.dart index 2882077b422..0f9593499a2 100644 --- a/app/lib/l10n/app_localizations_kn.dart +++ b/app/lib/l10n/app_localizations_kn.dart @@ -510,6 +510,12 @@ class AppLocalizationsKn extends AppLocalizations { @override String get doubleTap => 'ಗುಣ ಟ್ಯಾಪ್'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED ಝಗಬೆಳಗುವುದು'; diff --git a/app/lib/l10n/app_localizations_ko.dart b/app/lib/l10n/app_localizations_ko.dart index dbb99b1c515..78ec47a8905 100644 --- a/app/lib/l10n/app_localizations_ko.dart +++ b/app/lib/l10n/app_localizations_ko.dart @@ -499,6 +499,12 @@ class AppLocalizationsKo extends AppLocalizations { @override String get doubleTap => '더블 탭'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED 밝기'; diff --git a/app/lib/l10n/app_localizations_lt.dart b/app/lib/l10n/app_localizations_lt.dart index b82b7a6dae3..e4198dc46b5 100644 --- a/app/lib/l10n/app_localizations_lt.dart +++ b/app/lib/l10n/app_localizations_lt.dart @@ -509,6 +509,12 @@ class AppLocalizationsLt extends AppLocalizations { @override String get doubleTap => 'Dvigubas bakstelėjimas'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED ryškumas'; diff --git a/app/lib/l10n/app_localizations_lv.dart b/app/lib/l10n/app_localizations_lv.dart index 2e8039825c7..02bef182f61 100644 --- a/app/lib/l10n/app_localizations_lv.dart +++ b/app/lib/l10n/app_localizations_lv.dart @@ -510,6 +510,12 @@ class AppLocalizationsLv extends AppLocalizations { @override String get doubleTap => 'Dubultklikšķis'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED spilgtums'; diff --git a/app/lib/l10n/app_localizations_mk.dart b/app/lib/l10n/app_localizations_mk.dart index 139f297aec9..2f13b9556b6 100644 --- a/app/lib/l10n/app_localizations_mk.dart +++ b/app/lib/l10n/app_localizations_mk.dart @@ -511,6 +511,12 @@ class AppLocalizationsMk extends AppLocalizations { @override String get doubleTap => 'Двојно допирање'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Осветленост на LED'; diff --git a/app/lib/l10n/app_localizations_mr.dart b/app/lib/l10n/app_localizations_mr.dart index 8a1eff117e6..4e49da52d81 100644 --- a/app/lib/l10n/app_localizations_mr.dart +++ b/app/lib/l10n/app_localizations_mr.dart @@ -509,6 +509,12 @@ class AppLocalizationsMr extends AppLocalizations { @override String get doubleTap => 'दुहेरी टॅप'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED चमक'; diff --git a/app/lib/l10n/app_localizations_ms.dart b/app/lib/l10n/app_localizations_ms.dart index da2d43c5381..07c12cd492b 100644 --- a/app/lib/l10n/app_localizations_ms.dart +++ b/app/lib/l10n/app_localizations_ms.dart @@ -509,6 +509,12 @@ class AppLocalizationsMs extends AppLocalizations { @override String get doubleTap => 'Ketik Dua Kali'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Kecerahan LED'; diff --git a/app/lib/l10n/app_localizations_nl.dart b/app/lib/l10n/app_localizations_nl.dart index 13b3a3e5bf8..43a213abb08 100644 --- a/app/lib/l10n/app_localizations_nl.dart +++ b/app/lib/l10n/app_localizations_nl.dart @@ -510,6 +510,12 @@ class AppLocalizationsNl extends AppLocalizations { @override String get doubleTap => 'Dubbel tikken'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED-helderheid'; diff --git a/app/lib/l10n/app_localizations_no.dart b/app/lib/l10n/app_localizations_no.dart index a80d8e77b15..31bcf3df250 100644 --- a/app/lib/l10n/app_localizations_no.dart +++ b/app/lib/l10n/app_localizations_no.dart @@ -509,6 +509,12 @@ class AppLocalizationsNo extends AppLocalizations { @override String get doubleTap => 'Dobbelttrykk'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED-lysstyrke'; diff --git a/app/lib/l10n/app_localizations_pl.dart b/app/lib/l10n/app_localizations_pl.dart index 73590a6326a..54e1e8a877f 100644 --- a/app/lib/l10n/app_localizations_pl.dart +++ b/app/lib/l10n/app_localizations_pl.dart @@ -508,6 +508,12 @@ class AppLocalizationsPl extends AppLocalizations { @override String get doubleTap => 'Podwójne dotknięcie'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Jasność LED'; diff --git a/app/lib/l10n/app_localizations_pt.dart b/app/lib/l10n/app_localizations_pt.dart index 131620fd530..ae55f4702d6 100644 --- a/app/lib/l10n/app_localizations_pt.dart +++ b/app/lib/l10n/app_localizations_pt.dart @@ -506,6 +506,12 @@ class AppLocalizationsPt extends AppLocalizations { @override String get doubleTap => 'Toque duplo'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Brilho do LED'; diff --git a/app/lib/l10n/app_localizations_ro.dart b/app/lib/l10n/app_localizations_ro.dart index 6667b585157..5fc89c52386 100644 --- a/app/lib/l10n/app_localizations_ro.dart +++ b/app/lib/l10n/app_localizations_ro.dart @@ -511,6 +511,12 @@ class AppLocalizationsRo extends AppLocalizations { @override String get doubleTap => 'Dublă apăsare'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Luminozitate LED'; diff --git a/app/lib/l10n/app_localizations_ru.dart b/app/lib/l10n/app_localizations_ru.dart index 58a0d7cae2b..6aa135e849f 100644 --- a/app/lib/l10n/app_localizations_ru.dart +++ b/app/lib/l10n/app_localizations_ru.dart @@ -510,6 +510,12 @@ class AppLocalizationsRu extends AppLocalizations { @override String get doubleTap => 'Двойное нажатие'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Яркость LED'; diff --git a/app/lib/l10n/app_localizations_sk.dart b/app/lib/l10n/app_localizations_sk.dart index 8dfe811ba2e..40fbe56f9e2 100644 --- a/app/lib/l10n/app_localizations_sk.dart +++ b/app/lib/l10n/app_localizations_sk.dart @@ -510,6 +510,12 @@ class AppLocalizationsSk extends AppLocalizations { @override String get doubleTap => 'Dvojité ťuknutie'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Jas LED'; diff --git a/app/lib/l10n/app_localizations_sl.dart b/app/lib/l10n/app_localizations_sl.dart index 0081752980c..cd3505693bc 100644 --- a/app/lib/l10n/app_localizations_sl.dart +++ b/app/lib/l10n/app_localizations_sl.dart @@ -508,6 +508,12 @@ class AppLocalizationsSl extends AppLocalizations { @override String get doubleTap => 'Dvojni dotik'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Svetlost LED'; diff --git a/app/lib/l10n/app_localizations_sr.dart b/app/lib/l10n/app_localizations_sr.dart index ed61369b84a..3e16a907d95 100644 --- a/app/lib/l10n/app_localizations_sr.dart +++ b/app/lib/l10n/app_localizations_sr.dart @@ -508,6 +508,12 @@ class AppLocalizationsSr extends AppLocalizations { @override String get doubleTap => 'Дупло додирни'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Сјајност LED'; diff --git a/app/lib/l10n/app_localizations_sv.dart b/app/lib/l10n/app_localizations_sv.dart index 6415998d289..3f22a29801d 100644 --- a/app/lib/l10n/app_localizations_sv.dart +++ b/app/lib/l10n/app_localizations_sv.dart @@ -509,6 +509,12 @@ class AppLocalizationsSv extends AppLocalizations { @override String get doubleTap => 'Dubbeltryck'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED-ljusstyrka'; diff --git a/app/lib/l10n/app_localizations_ta.dart b/app/lib/l10n/app_localizations_ta.dart index f46917ed97a..0e3a9664c34 100644 --- a/app/lib/l10n/app_localizations_ta.dart +++ b/app/lib/l10n/app_localizations_ta.dart @@ -511,6 +511,12 @@ class AppLocalizationsTa extends AppLocalizations { @override String get doubleTap => 'இரட்டை தட்டு'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED பிரகாசம்'; diff --git a/app/lib/l10n/app_localizations_te.dart b/app/lib/l10n/app_localizations_te.dart index 7e63976ffce..b093f90bd24 100644 --- a/app/lib/l10n/app_localizations_te.dart +++ b/app/lib/l10n/app_localizations_te.dart @@ -510,6 +510,12 @@ class AppLocalizationsTe extends AppLocalizations { @override String get doubleTap => 'డబల్ నొక్కండి'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED ప్రకాశం'; diff --git a/app/lib/l10n/app_localizations_th.dart b/app/lib/l10n/app_localizations_th.dart index aa29aeebdf3..8b73041d298 100644 --- a/app/lib/l10n/app_localizations_th.dart +++ b/app/lib/l10n/app_localizations_th.dart @@ -506,6 +506,12 @@ class AppLocalizationsTh extends AppLocalizations { @override String get doubleTap => 'แตะสองครั้ง'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'ความสว่าง LED'; diff --git a/app/lib/l10n/app_localizations_tl.dart b/app/lib/l10n/app_localizations_tl.dart index b78d67883b1..4901bd8428e 100644 --- a/app/lib/l10n/app_localizations_tl.dart +++ b/app/lib/l10n/app_localizations_tl.dart @@ -510,6 +510,12 @@ class AppLocalizationsTl extends AppLocalizations { @override String get doubleTap => 'Double Tap'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED Brightness'; diff --git a/app/lib/l10n/app_localizations_tr.dart b/app/lib/l10n/app_localizations_tr.dart index 2bb2f5110af..90733732ed4 100644 --- a/app/lib/l10n/app_localizations_tr.dart +++ b/app/lib/l10n/app_localizations_tr.dart @@ -510,6 +510,12 @@ class AppLocalizationsTr extends AppLocalizations { @override String get doubleTap => 'Çift Dokunma'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED Parlaklığı'; diff --git a/app/lib/l10n/app_localizations_uk.dart b/app/lib/l10n/app_localizations_uk.dart index a26e6ed5da4..bdf69149116 100644 --- a/app/lib/l10n/app_localizations_uk.dart +++ b/app/lib/l10n/app_localizations_uk.dart @@ -510,6 +510,12 @@ class AppLocalizationsUk extends AppLocalizations { @override String get doubleTap => 'Подвійне натискання'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Яскравість світлодіода'; diff --git a/app/lib/l10n/app_localizations_ur.dart b/app/lib/l10n/app_localizations_ur.dart index bd5264e3149..6a3f66ef0c6 100644 --- a/app/lib/l10n/app_localizations_ur.dart +++ b/app/lib/l10n/app_localizations_ur.dart @@ -508,6 +508,12 @@ class AppLocalizationsUr extends AppLocalizations { @override String get doubleTap => 'دوگنا تھپتھپائیں'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED روشنی'; diff --git a/app/lib/l10n/app_localizations_vi.dart b/app/lib/l10n/app_localizations_vi.dart index 43f3e215500..483f6074ff6 100644 --- a/app/lib/l10n/app_localizations_vi.dart +++ b/app/lib/l10n/app_localizations_vi.dart @@ -510,6 +510,12 @@ class AppLocalizationsVi extends AppLocalizations { @override String get doubleTap => 'Nhấn đúp'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'Độ sáng đèn LED'; diff --git a/app/lib/l10n/app_localizations_zh.dart b/app/lib/l10n/app_localizations_zh.dart index d602455d939..2d5fdbe1142 100644 --- a/app/lib/l10n/app_localizations_zh.dart +++ b/app/lib/l10n/app_localizations_zh.dart @@ -499,6 +499,12 @@ class AppLocalizationsZh extends AppLocalizations { @override String get doubleTap => '双击'; + @override + String get singleTap => 'Single Tap'; + + @override + String get tripleTap => 'Triple Tap'; + @override String get ledBrightness => 'LED 亮度'; diff --git a/app/lib/pages/settings/device_settings.dart b/app/lib/pages/settings/device_settings.dart index 00c66a18411..b3d7ad8d016 100644 --- a/app/lib/pages/settings/device_settings.dart +++ b/app/lib/pages/settings/device_settings.dart @@ -341,11 +341,11 @@ class _DeviceSettingsState extends State { String _buttonPressTitle(OmiButtonPress press) { switch (press) { case OmiButtonPress.singleTap: - return '1x'; + return context.l10n.singleTap; case OmiButtonPress.doubleTap: return context.l10n.doubleTap; case OmiButtonPress.tripleTap: - return '3x'; + return context.l10n.tripleTap; } } diff --git a/app/lib/providers/capture_provider.dart b/app/lib/providers/capture_provider.dart index f7123b42651..c9ec3199ec7 100644 --- a/app/lib/providers/capture_provider.dart +++ b/app/lib/providers/capture_provider.dart @@ -644,6 +644,15 @@ class CaptureProvider extends ChangeNotifier }; } + String _buttonPressAnalyticsValue(String source) => source.replaceAll(' ', '_'); + + void _trackButtonAction(String source, String feature) { + PlatformManager.instance.analytics.omiButtonPress( + press: _buttonPressAnalyticsValue(source), + feature: feature, + ); + } + Future _runButtonAction({ required OmiButtonAction action, required String deviceId, @@ -663,10 +672,10 @@ class CaptureProvider extends ChangeNotifier _isProcessingButtonEvent = true; try { if (_isPaused) { - PlatformManager.instance.analytics.omiDoubleTap(feature: 'unmute'); + _trackButtonAction(source, 'unmute'); await resumeDeviceRecording(); } else { - PlatformManager.instance.analytics.omiDoubleTap(feature: 'mute'); + _trackButtonAction(source, 'mute'); await pauseDeviceRecording(); } } catch (e) { @@ -679,11 +688,11 @@ class CaptureProvider extends ChangeNotifier Logger.debug("$source: toggling conversation star"); if (!_starOngoingConversation) { markConversationForStarring(); - PlatformManager.instance.analytics.omiDoubleTap(feature: 'star_conversation'); + _trackButtonAction(source, 'star_conversation'); HapticFeedback.mediumImpact(); } else { unmarkConversationForStarring(); - PlatformManager.instance.analytics.omiDoubleTap(feature: 'unstar_conversation'); + _trackButtonAction(source, 'unstar_conversation'); HapticFeedback.lightImpact(); } return; @@ -691,7 +700,7 @@ class CaptureProvider extends ChangeNotifier Logger.debug("$source: processing conversation"); _isProcessingButtonEvent = true; try { - PlatformManager.instance.analytics.omiDoubleTap(feature: 'process_conversation'); + _trackButtonAction(source, 'process_conversation'); await forceProcessingCurrentConversation(); } finally { _isProcessingButtonEvent = false; diff --git a/app/lib/utils/analytics/analytics_manager.dart b/app/lib/utils/analytics/analytics_manager.dart index 5258677fd33..65319aa627a 100644 --- a/app/lib/utils/analytics/analytics_manager.dart +++ b/app/lib/utils/analytics/analytics_manager.dart @@ -1346,6 +1346,21 @@ class AnalyticsManager { ); } + void omiButtonPress({ + required String press, + required String feature, + Map? additionalProperties, + }) { + track( + 'Omi Button Press', + properties: { + 'press': press, + 'feature': feature, + if (additionalProperties != null) ...additionalProperties, + }, + ); + } + // ============================================================================ // WRAPPED 2025 TRACKING // ============================================================================