diff --git a/.github/actions/build_android/action.yml b/.github/actions/build_android/action.yml index 93cc6493b..0db0659eb 100644 --- a/.github/actions/build_android/action.yml +++ b/.github/actions/build_android/action.yml @@ -45,7 +45,7 @@ runs: ANDROID_KEY_JKS: ${{ inputs.android-key-jks }} - name: Build APK shell: bash - run: flutter build apk --split-debug-info=./symbols --build-number="$BUILD_NUMBER" --build-name="$VERSION" --dart-define=cronetHttpNoPlay=true --dart-define=SENTRY_DSN=$SENTRY_DSN + run: flutter build apk --split-debug-info=./symbols --build-number="$BUILD_NUMBER" --build-name="$VERSION" --dart-define=cronetHttpNoPlay=true --dart-define=SENTRY_DSN="$SENTRY_DSN" env: VERSION: ${{ inputs.version }} BUILD_NUMBER: ${{ inputs.build-number }} @@ -56,7 +56,7 @@ runs: #SENTRY_URL: ${{ inputs.sentry-url }} - name: Build AppBundle shell: bash - run: flutter build appbundle --split-debug-info=./symbols --build-number="$BUILD_NUMBER" --build-name="$VERSION" --dart-define=SENTRY_DSN=$SENTRY_DSN + run: flutter build appbundle --split-debug-info=./symbols --build-number="$BUILD_NUMBER" --build-name="$VERSION" --dart-define=SENTRY_DSN="$SENTRY_DSN" env: VERSION: ${{ inputs.version }} BUILD_NUMBER: ${{ inputs.build-number }} diff --git a/analysis_options.yaml b/analysis_options.yaml index ab8a3ce75..1fbf1972d 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -22,13 +22,57 @@ linter: # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + - cancel_subscriptions + - close_sinks + - discarded_futures + - no_adjacent_strings_in_list + - no_self_assignments + - unnecessary_statements + - always_declare_return_types + - always_put_required_named_parameters_first + - always_put_control_body_on_new_line + - avoid_multiple_declarations_per_line + - avoid_positional_boolean_parameters + - avoid_returning_this + - avoid_unused_constructor_parameters + - avoid_void_async + - cascade_invocations + - cast_nullable_to_non_nullable + - join_return_with_assignment + - missing_whitespace_between_adjacent_strings + - one_member_abstracts + - prefer_constructors_over_static_methods + - require_trailing_commas + - unnecessary_await_in_return + - unnecessary_breaks + - unnecessary_lambdas + - unnecessary_raw_strings + - use_enums + - use_if_null_to_convert_nulls_to_bools + - avoid_slow_async_io + - avoid_type_to_string + - literal_only_boolean_expressions + - prefer_void_to_null + - prefer_relative_imports + - test_types_in_equals # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options analyzer: - exclude: [ build/**, pub_cache/** ] + exclude: + - build/** + - pub_cache/** + - lib/**.g.dart + - lib/l10n/** + language: #strict-casts: true - strict-raw-types: true \ No newline at end of file + strict-raw-types: true + plugins: + - custom_lint +custom_lint: + rules: + # Explicitly disable one lint rule + - missing_provider_scope: false \ No newline at end of file diff --git a/lib/Backend/Bluetooth/bluetooth_manager.dart b/lib/Backend/Bluetooth/bluetooth_manager.dart index 269245c67..ed627ff46 100644 --- a/lib/Backend/Bluetooth/bluetooth_manager.dart +++ b/lib/Backend/Bluetooth/bluetooth_manager.dart @@ -2,11 +2,11 @@ import 'dart:async'; import 'package:logging/logging.dart' as log; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:tail_app/Backend/logging_wrappers.dart'; import '../../constants.dart'; import '../Definitions/Device/device_definition.dart'; import '../device_registry.dart'; +import '../logging_wrappers.dart'; part 'bluetooth_manager.g.dart'; @@ -44,8 +44,7 @@ class KnownDevices extends _$KnownDevices { } Future remove(String id) async { - Map state2 = Map.from(state); - state2.remove(id); + Map state2 = Map.from(state)..remove(id); state = state2; await store(); } diff --git a/lib/Backend/Bluetooth/bluetooth_manager_plus.dart b/lib/Backend/Bluetooth/bluetooth_manager_plus.dart index d44616188..1ee157710 100644 --- a/lib/Backend/Bluetooth/bluetooth_manager_plus.dart +++ b/lib/Backend/Bluetooth/bluetooth_manager_plus.dart @@ -9,17 +9,17 @@ import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:logging/logging.dart' as log; import 'package:permission_handler/permission_handler.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_utils.dart'; -import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; -import 'package:tail_app/Backend/device_registry.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import '../../Frontend/utils.dart'; import '../../constants.dart'; +import '../Definitions/Device/device_definition.dart'; +import '../device_registry.dart'; import '../logging_wrappers.dart'; import '../sensors.dart'; import 'bluetooth_manager.dart'; import 'bluetooth_message.dart'; +import 'bluetooth_utils.dart'; part 'bluetooth_manager_plus.g.dart'; @@ -86,8 +86,7 @@ Future initFlutterBluePlus(InitFlutterBluePlusRef ref) async { baseStoredDevice = statefulDevice.baseStoredDevice; //transaction.setTag('Known Device', 'Yes'); } else { - baseStoredDevice = BaseStoredDevice(deviceDefinition.uuid, deviceID, deviceDefinition.deviceType.color(ref: ref).value); - baseStoredDevice.name = getNameFromBTName(deviceDefinition.btName); + baseStoredDevice = BaseStoredDevice(deviceDefinition.uuid, deviceID, deviceDefinition.deviceType.color(ref: ref).value)..name = getNameFromBTName(deviceDefinition.btName); statefulDevice = BaseStatefulDevice(deviceDefinition, baseStoredDevice); //transaction.setTag('Known Device', 'No'); Future(() => ref.read(knownDevicesProvider.notifier).add(statefulDevice)); @@ -109,8 +108,9 @@ Future initFlutterBluePlus(InitFlutterBluePlusRef ref) async { } if (Platform.isAndroid) { //start foreground service on device connected. Library handles duplicate start calls - _bluetoothPlusLogger.fine('Requesting notification permission'); - _bluetoothPlusLogger.finer('Requesting notification permission result${await Permission.notification.request()}'); // Used only for Foreground service + _bluetoothPlusLogger + ..fine('Requesting notification permission') + ..finer('Requesting notification permission result${await Permission.notification.request()}'); // Used only for Foreground service FlutterForegroundTask.init( androidNotificationOptions: AndroidNotificationOptions( channelId: 'foreground_service', @@ -275,23 +275,26 @@ Future initFlutterBluePlus(InitFlutterBluePlusRef ref) async { } } }, - onError: (e) => _bluetoothPlusLogger.severe(e), + onError: (e, s) => _bluetoothPlusLogger.severe("", e, s), ); - _keepAliveStreamSubscription = Stream.periodic(const Duration(seconds: 15)).listen((event) async { - Map knownDevices = ref.read(knownDevicesProvider); - for (var element in flutterBluePlus.connectedDevices) { - BaseStatefulDevice? device = knownDevices[element.remoteId.str]; - if (device != null) { - device.commandQueue.addCommand(BluetoothMessage(message: "PING", device: device, priority: Priority.low, type: CommandType.system)); - device.commandQueue.addCommand(BluetoothMessage(message: "BATT", device: device, priority: Priority.low, type: CommandType.system)); - element.readRssi(); - if (device.baseDeviceDefinition.deviceType != DeviceType.ears && device.hasGlowtip.value == GlowtipStatus.unknown) { - device.commandQueue.addCommand(BluetoothMessage(message: "VER", device: device, priority: Priority.low, type: CommandType.system)); + _keepAliveStreamSubscription = Stream.periodic(const Duration(seconds: 15)).listen( + (event) async { + Map knownDevices = ref.read(knownDevicesProvider); + for (var element in flutterBluePlus.connectedDevices) { + BaseStatefulDevice? device = knownDevices[element.remoteId.str]; + if (device != null) { + device.commandQueue.addCommand(BluetoothMessage(message: "PING", device: device, priority: Priority.low, type: CommandType.system)); + device.commandQueue.addCommand(BluetoothMessage(message: "BATT", device: device, priority: Priority.low, type: CommandType.system)); + element.readRssi(); + if (device.baseDeviceDefinition.deviceType != DeviceType.ears && device.hasGlowtip.value == GlowtipStatus.unknown) { + device.commandQueue.addCommand(BluetoothMessage(message: "VER", device: device, priority: Priority.low, type: CommandType.system)); + } } } - } - }, cancelOnError: true); + }, + cancelOnError: true, + ); // Shut down bluetooth related things ref.onDispose(() async { @@ -354,7 +357,7 @@ Future connect(String id) async { Future beginScan({Duration? timeout}) async { if (_didInitFlutterBluePlus && !flutterBluePlus.isScanningNow) { _bluetoothPlusLogger.info("Starting scan"); - await flutterBluePlus.startScan(withServices: DeviceRegistry.getAllIds().map((e) => Guid(e)).toList(), continuousUpdates: timeout == null, androidScanMode: AndroidScanMode.lowPower, timeout: timeout); + await flutterBluePlus.startScan(withServices: DeviceRegistry.getAllIds().map(Guid.new).toList(), continuousUpdates: timeout == null, androidScanMode: AndroidScanMode.lowPower, timeout: timeout); } } @@ -392,8 +395,7 @@ Future sendMessage(BaseStatefulDevice device, List message, {bool wit return; } - Future future = bluetoothCharacteristic.write(message, withoutResponse: withoutResponse && bluetoothCharacteristic.properties.writeWithoutResponse); - future.catchError((e) { + Future future = bluetoothCharacteristic.write(message, withoutResponse: withoutResponse && bluetoothCharacteristic.properties.writeWithoutResponse).catchError((e) { _bluetoothPlusLogger.severe("Unable to send message to ${device.baseDeviceDefinition.btName} $e", e); }); await future; diff --git a/lib/Backend/Bluetooth/bluetooth_message.dart b/lib/Backend/Bluetooth/bluetooth_message.dart index 166fb9254..b08df915b 100644 --- a/lib/Backend/Bluetooth/bluetooth_message.dart +++ b/lib/Backend/Bluetooth/bluetooth_message.dart @@ -1,6 +1,6 @@ import 'dart:core'; -import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; +import '../Definitions/Device/device_definition.dart'; enum Priority { low, normal, high } diff --git a/lib/Backend/Bluetooth/bluetooth_utils.dart b/lib/Backend/Bluetooth/bluetooth_utils.dart index ff2dee816..2a2171552 100644 --- a/lib/Backend/Bluetooth/bluetooth_utils.dart +++ b/lib/Backend/Bluetooth/bluetooth_utils.dart @@ -3,7 +3,7 @@ import 'package:flutter_blue_plus/flutter_blue_plus.dart'; /// Wrapper for FlutterBluePlus in order to easily mock it /// Wraps all static calls for testing purposes class FlutterBluePlusMockable { - get onScanResults => FlutterBluePlus.onScanResults; + Stream> get onScanResults => FlutterBluePlus.onScanResults; Future startScan({ List withServices = const [], diff --git a/lib/Backend/Definitions/Action/base_action.dart b/lib/Backend/Definitions/Action/base_action.dart index a71683a5c..23c770dd2 100644 --- a/lib/Backend/Definitions/Action/base_action.dart +++ b/lib/Backend/Definitions/Action/base_action.dart @@ -1,7 +1,7 @@ import 'package:hive/hive.dart'; -import 'package:tail_app/Frontend/translation_string_definitions.dart'; import 'package:uuid/uuid.dart'; +import '../../../Frontend/translation_string_definitions.dart'; import '../Device/device_definition.dart'; part 'base_action.g.dart'; @@ -92,7 +92,7 @@ class CommandAction extends BaseAction { final String command; final String? response; - CommandAction({required this.command, this.response, required super.name, required super.deviceCategory, required super.actionCategory, required super.uuid, super.nameAlias}); + CommandAction({required this.command, required super.name, required super.deviceCategory, required super.actionCategory, required super.uuid, this.response, super.nameAlias}); factory CommandAction.hiddenEars(String command, String response) { return CommandAction(command: command, response: response, deviceCategory: [DeviceType.ears], actionCategory: ActionCategory.hidden, uuid: const Uuid().v4(), name: command); @@ -104,5 +104,5 @@ class AudioAction extends BaseAction { @HiveField(5) String file; - AudioAction({required super.name, super.deviceCategory = DeviceType.values, super.actionCategory = ActionCategory.audio, required super.uuid, required this.file}); + AudioAction({required super.name, required super.uuid, required this.file, super.deviceCategory = DeviceType.values, super.actionCategory = ActionCategory.audio}); } diff --git a/lib/Backend/Definitions/Device/device_definition.dart b/lib/Backend/Definitions/Device/device_definition.dart index aa314a005..c9123e3b4 100644 --- a/lib/Backend/Definitions/Device/device_definition.dart +++ b/lib/Backend/Definitions/Device/device_definition.dart @@ -11,13 +11,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive/hive.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager_plus.dart'; -import 'package:tail_app/Backend/firmware_update.dart'; import '../../../Frontend/translation_string_definitions.dart'; import '../../../Frontend/utils.dart'; +import '../../Bluetooth/bluetooth_manager.dart'; +import '../../Bluetooth/bluetooth_manager_plus.dart'; import '../../Bluetooth/bluetooth_message.dart'; +import '../../firmware_update.dart'; part 'device_definition.g.dart'; @@ -149,8 +149,9 @@ class BaseStatefulDevice extends ChangeNotifier { } else if (deviceConnectionState.value == ConnectivityState.connected) { // Add initial commands to the queue Future.delayed(const Duration(seconds: 2), () { - commandQueue.addCommand(BluetoothMessage(message: "VER", device: this, priority: Priority.low, type: CommandType.system)); - commandQueue.addCommand(BluetoothMessage(message: "HWVER", device: this, priority: Priority.low, type: CommandType.system)); + commandQueue + ..addCommand(BluetoothMessage(message: "VER", device: this, priority: Priority.low, type: CommandType.system)) + ..addCommand(BluetoothMessage(message: "HWVER", device: this, priority: Priority.low, type: CommandType.system)); }); } }); @@ -182,11 +183,11 @@ class BaseStatefulDevice extends ChangeNotifier { Future getFirmwareInfo() async { // Try to get firmware update information from Tail Company site if (baseDeviceDefinition.fwURL != "" && fwInfo.value == null) { - Future> valueFuture = (await initDio()).get(baseDeviceDefinition.fwURL, options: Options(responseType: ResponseType.json)); - valueFuture.onError((error, stackTrace) { - bluetoothLog.warning("Unable to get Firmware info for ${baseDeviceDefinition.fwURL} :$error", error, stackTrace); - return Response(requestOptions: RequestOptions(), statusCode: 500); - }); + Future> valueFuture = (await initDio()).get(baseDeviceDefinition.fwURL, options: Options(responseType: ResponseType.json)) + ..onError((error, stackTrace) { + bluetoothLog.warning("Unable to get Firmware info for ${baseDeviceDefinition.fwURL} :$error", error, stackTrace); + return Response(requestOptions: RequestOptions(), statusCode: 500); + }); Response value = await valueFuture; if (value.statusCode == 200) { fwInfo.value = FWInfo.fromJson(const JsonDecoder().convert(value.data.toString())); @@ -308,16 +309,19 @@ class CommandQueue { // We use a timeout as sometimes a response isn't sent by the gear timer = Timer(timeoutDuration, () {}); response = device.rxCharacteristicStream - .timeout(timeoutDuration, onTimeout: (sink) { - sink.addError(""); - }) + .timeout( + timeoutDuration, + onTimeout: (sink) { + sink.addError(""); + }, + ) .where((event) { bluetoothLog.info('Response:$event'); return event.contains(message.responseMSG!); }) .handleError((string) => "") - .first; - response.catchError((string) => ""); + .first + ..catchError((string) => ""); } await sendMessage(device, const Utf8Encoder().convert(message.message)); device.messageHistory.add(MessageHistoryEntry(type: MessageHistoryType.send, message: message.message)); diff --git a/lib/Backend/NavigationObserver/custom_go_router_navigation_observer.dart b/lib/Backend/NavigationObserver/custom_go_router_navigation_observer.dart index 2e1ce6e6a..d52d25389 100644 --- a/lib/Backend/NavigationObserver/custom_go_router_navigation_observer.dart +++ b/lib/Backend/NavigationObserver/custom_go_router_navigation_observer.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:plausible_analytics/plausible_analytics.dart'; @@ -13,7 +15,7 @@ class CustomNavObserver extends NavigatorObserver { String? name = route.settings.name; String refferalName = previousRoute?.settings.name ?? ""; if (name != null) { - plausible.event(page: route.settings.name.toString(), referrer: refferalName); + unawaited(plausible.event(page: route.settings.name.toString(), referrer: refferalName)); } } } diff --git a/lib/Backend/action_registry.dart b/lib/Backend/action_registry.dart index 736988322..61b9e3dcf 100644 --- a/lib/Backend/action_registry.dart +++ b/lib/Backend/action_registry.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:tail_app/Backend/audio.dart'; -import 'package:tail_app/Backend/move_lists.dart'; import 'Bluetooth/bluetooth_manager.dart'; import 'Bluetooth/bluetooth_manager_plus.dart'; import 'Definitions/Action/base_action.dart'; import 'Definitions/Device/device_definition.dart'; +import 'audio.dart'; +import 'move_lists.dart'; part 'action_registry.g.dart'; @@ -276,7 +276,8 @@ Map> getAvailableActions(GetAvailableActionsRef Map knownDevices = ref.watch(knownDevicesProvider); Map> sortedActions = {}; for (BaseAction baseAction in List.from(ActionRegistry.allCommands) - ..addAll(ref.read(moveListsProvider))..addAll(ref.read(userAudioActionsProvider))) { + ..addAll(ref.read(moveListsProvider)) + ..addAll(ref.read(userAudioActionsProvider))) { Set? baseActions = {}; for (BaseStatefulDevice baseStatefulDevice in knownDevices.values.where((element) => element.deviceConnectionState.value == ConnectivityState.connected)) { // check if command matches device type @@ -301,13 +302,11 @@ Map> getAvailableActions(GetAvailableActionsRef Map> getAllActions(GetAllActionsRef ref, Set deviceType) { Map> sortedActions = {}; for (BaseAction baseAction in List.from(ActionRegistry.allCommands) - ..addAll(ref.read(moveListsProvider))..addAll(ref.read(userAudioActionsProvider))) { + ..addAll(ref.read(moveListsProvider)) + ..addAll(ref.read(userAudioActionsProvider))) { Set? baseActions = {}; // check if command matches device type - if (baseAction.deviceCategory - .toSet() - .intersection(deviceType) - .isNotEmpty) { + if (baseAction.deviceCategory.toSet().intersection(deviceType).isNotEmpty) { // get category if it exists if (sortedActions.containsKey(baseAction.actionCategory)) { baseActions = sortedActions[baseAction.actionCategory]; diff --git a/lib/Backend/app_shortcuts.dart b/lib/Backend/app_shortcuts.dart index 14b2032b5..fb4113c82 100644 --- a/lib/Backend/app_shortcuts.dart +++ b/lib/Backend/app_shortcuts.dart @@ -1,13 +1,14 @@ +import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quick_actions/quick_actions.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart'; -import 'package:tail_app/Backend/Definitions/Action/base_action.dart'; -import 'package:tail_app/Backend/move_lists.dart'; -import 'package:tail_app/Backend/sensors.dart'; +import 'Bluetooth/bluetooth_manager.dart'; +import 'Definitions/Action/base_action.dart'; import 'Definitions/Device/device_definition.dart'; import 'favorite_actions.dart'; +import 'move_lists.dart'; +import 'sensors.dart'; part 'app_shortcuts.g.dart'; @@ -29,13 +30,17 @@ Future appShortcuts(AppShortcutsRef ref) async { } Future updateShortcuts(List favoriteActions, Ref ref) async { - Iterable allActions = favoriteActions.map( - (e) => ref.read(getActionFromUUIDProvider(e.actionUUID)) as BaseAction, - ); - - quickActions.setShortcutItems(allActions + Iterable allActions = favoriteActions .map( - (e) => ShortcutItem(type: e.uuid, localizedTitle: e.name), + (e) => ref.read(getActionFromUUIDProvider(e.actionUUID)), ) - .toList()); + .whereNotNull(); + + quickActions.setShortcutItems( + allActions + .map( + (e) => ShortcutItem(type: e.uuid, localizedTitle: e.name), + ) + .toList(), + ); } diff --git a/lib/Backend/audio.dart b/lib/Backend/audio.dart index 9ca37a3d1..85ac56612 100644 --- a/lib/Backend/audio.dart +++ b/lib/Backend/audio.dart @@ -2,9 +2,9 @@ import 'package:audio_session/audio_session.dart'; import 'package:just_audio/just_audio.dart'; import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:tail_app/Backend/Definitions/Action/base_action.dart'; import '../constants.dart'; +import 'Definitions/Action/base_action.dart'; import 'logging_wrappers.dart'; part 'audio.g.dart'; @@ -14,18 +14,20 @@ final Logger _audioLogger = Logger('Audio'); Future setUpAudio() async { _audioLogger.info("Setting up audio session"); final session = await AudioSession.instance; - await session.configure(const AudioSessionConfiguration( - avAudioSessionCategory: AVAudioSessionCategory.ambient, - avAudioSessionMode: AVAudioSessionMode.defaultMode, - avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none, - androidAudioAttributes: AndroidAudioAttributes( - contentType: AndroidAudioContentType.unknown, - flags: AndroidAudioFlags.none, - usage: AndroidAudioUsage.media, + await session.configure( + const AudioSessionConfiguration( + avAudioSessionCategory: AVAudioSessionCategory.ambient, + avAudioSessionMode: AVAudioSessionMode.defaultMode, + avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none, + androidAudioAttributes: AndroidAudioAttributes( + contentType: AndroidAudioContentType.unknown, + flags: AndroidAudioFlags.none, + usage: AndroidAudioUsage.media, + ), + androidAudioFocusGainType: AndroidAudioFocusGainType.gain, + androidWillPauseWhenDucked: true, ), - androidAudioFocusGainType: AndroidAudioFocusGainType.gain, - androidWillPauseWhenDucked: true, - )); + ); } Future playSound(String file) async { @@ -51,13 +53,14 @@ class UserAudioActions extends _$UserAudioActions { return results; } - void add(AudioAction action) { - state.add(action); - state.sort(); + Future add(AudioAction action) async { + state + ..add(action) + ..sort(); store(); } - void remove(AudioAction action) { + Future remove(AudioAction action) async { state.removeWhere((element) => element.uuid == action.uuid); store(); } diff --git a/lib/Backend/background_update.dart b/lib/Backend/background_update.dart index 896d7cd27..ba89ce261 100644 --- a/lib/Backend/background_update.dart +++ b/lib/Backend/background_update.dart @@ -3,8 +3,8 @@ import 'package:awesome_notifications/awesome_notifications.dart'; import 'package:background_fetch/background_fetch.dart'; import 'package:logging/logging.dart'; -import 'package:tail_app/Frontend/utils.dart'; -import 'package:tail_app/constants.dart'; +import '../Frontend/utils.dart'; +import '../constants.dart'; import 'package:wordpress_client/wordpress_client.dart'; import 'logging_wrappers.dart'; @@ -30,7 +30,6 @@ Future initBackgroundTasks() async { switch (taskId) { case 'com.codel1417.tail_app.blog': checkForNewPosts(); - break; default: _backgroundLogger.info("Default fetch task"); } diff --git a/lib/Backend/device_registry.dart b/lib/Backend/device_registry.dart index e73af7df4..ea3a8da47 100644 --- a/lib/Backend/device_registry.dart +++ b/lib/Backend/device_registry.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:logging/logging.dart' as log; import 'package:pub_semver/pub_semver.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart'; -import 'package:tail_app/Backend/Definitions/Action/base_action.dart'; -import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; +import 'Bluetooth/bluetooth_manager.dart'; +import 'Definitions/Action/base_action.dart'; +import 'Definitions/Device/device_definition.dart'; part 'device_registry.g.dart'; @@ -14,22 +14,24 @@ final deviceRegistryLogger = log.Logger('DeviceRegistry'); class DeviceRegistry { static Set allDevices = { BaseDeviceDefinition( - uuid: "798e1528-2832-4a87-93d7-4d1b25a2f418", - btName: "MiTail", - bleDeviceService: "3af2108b-d066-42da-a7d4-55648fa0a9b6", - bleRxCharacteristic: "c6612b64-0087-4974-939e-68968ef294b0", - bleTxCharacteristic: "5bfd6484-ddee-4723-bfe6-b653372bbfd6", - deviceType: DeviceType.tail, - fwURL: "https://thetailcompany.com/fw/mitailfw", - minVersion: Version(5, 0, 0)), + uuid: "798e1528-2832-4a87-93d7-4d1b25a2f418", + btName: "MiTail", + bleDeviceService: "3af2108b-d066-42da-a7d4-55648fa0a9b6", + bleRxCharacteristic: "c6612b64-0087-4974-939e-68968ef294b0", + bleTxCharacteristic: "5bfd6484-ddee-4723-bfe6-b653372bbfd6", + deviceType: DeviceType.tail, + fwURL: "https://thetailcompany.com/fw/mitailfw", + minVersion: Version(5, 0, 0), + ), const BaseDeviceDefinition( - uuid: "9c5f3692-1c6e-4d46-b607-4f6f4a6e28ee", - btName: "(!)Tail1", - bleDeviceService: "3af2108b-d066-42da-a7d4-55648fa0a9b6", - bleRxCharacteristic: "c6612b64-0087-4974-939e-68968ef294b0", - bleTxCharacteristic: "5bfd6484-ddee-4723-bfe6-b653372bbfd6", - deviceType: DeviceType.tail, - unsupported: true), + uuid: "9c5f3692-1c6e-4d46-b607-4f6f4a6e28ee", + btName: "(!)Tail1", + bleDeviceService: "3af2108b-d066-42da-a7d4-55648fa0a9b6", + bleRxCharacteristic: "c6612b64-0087-4974-939e-68968ef294b0", + bleTxCharacteristic: "5bfd6484-ddee-4723-bfe6-b653372bbfd6", + deviceType: DeviceType.tail, + unsupported: true, + ), BaseDeviceDefinition( uuid: "5fb21175-fef4-448a-a38b-c472d935abab", btName: "minitail", @@ -67,7 +69,7 @@ class DeviceRegistry { bleTxCharacteristic: "05e026d8-b395-4416-9f8a-c00d6c3781b9", deviceType: DeviceType.ears, unsupported: true, - ) + ), }; static BaseDeviceDefinition getByUUID(String uuid) { diff --git a/lib/Backend/dynamic_config.dart b/lib/Backend/dynamic_config.dart index b129a8e4f..0c1349911 100644 --- a/lib/Backend/dynamic_config.dart +++ b/lib/Backend/dynamic_config.dart @@ -6,9 +6,9 @@ import 'package:flutter/services.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:logging/logging.dart'; import 'package:package_info_plus/package_info_plus.dart'; -import 'package:tail_app/Backend/logging_wrappers.dart'; -import 'package:tail_app/Frontend/utils.dart'; -import 'package:tail_app/constants.dart'; +import 'logging_wrappers.dart'; +import '../Frontend/utils.dart'; +import '../constants.dart'; import '../gen/assets.gen.dart'; diff --git a/lib/Backend/favorite_actions.dart b/lib/Backend/favorite_actions.dart index d7e8a1377..f70a6b462 100644 --- a/lib/Backend/favorite_actions.dart +++ b/lib/Backend/favorite_actions.dart @@ -1,7 +1,7 @@ import 'package:hive/hive.dart'; import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:tail_app/Backend/wear_bridge.dart'; +import 'wear_bridge.dart'; import '../constants.dart'; import 'Definitions/Action/base_action.dart'; @@ -42,8 +42,9 @@ class FavoriteActions extends _$FavoriteActions { } Future add(BaseAction action) async { - state.add(FavoriteAction(actionUUID: action.uuid, id: state.length + 1)); - state.sort(); + state + ..add(FavoriteAction(actionUUID: action.uuid, id: state.length + 1)) + ..sort(); await store(); } diff --git a/lib/Backend/move_lists.dart b/lib/Backend/move_lists.dart index a67bef311..6bb16e3c4 100644 --- a/lib/Backend/move_lists.dart +++ b/lib/Backend/move_lists.dart @@ -3,14 +3,14 @@ import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_message.dart'; -import 'package:tail_app/Backend/Definitions/Action/base_action.dart'; -import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; -import 'package:tail_app/Backend/audio.dart'; -import 'package:tail_app/Frontend/translation_string_definitions.dart'; +import '../Frontend/translation_string_definitions.dart'; import '../constants.dart'; import '../main.dart'; +import 'Bluetooth/bluetooth_message.dart'; +import 'Definitions/Action/base_action.dart'; +import 'Definitions/Device/device_definition.dart'; +import 'audio.dart'; import 'logging_wrappers.dart'; part 'move_lists.g.dart'; @@ -82,9 +82,8 @@ extension MoveTypeExtension on MoveType { case MoveType.delay: return Icons.timelapse; case MoveType.home: - Icons.home; + return Icons.home; } - return Icons.question_mark; } } @@ -148,7 +147,7 @@ class MoveList extends BaseAction { @HiveField(6) double repeat = 1; - MoveList({required super.name, required super.deviceCategory, super.actionCategory = ActionCategory.sequence, required super.uuid, this.moves = const []}) { + MoveList({required super.name, required super.deviceCategory, required super.uuid, super.actionCategory = ActionCategory.sequence, this.moves = const []}) { if (moves.isEmpty) { moves = []; } @@ -158,7 +157,7 @@ class MoveList extends BaseAction { class EarsMoveList extends MoveList { List commandMoves = []; - EarsMoveList({required super.name, super.deviceCategory = const [DeviceType.ears], required super.uuid, super.actionCategory = ActionCategory.ears, required this.commandMoves}); + EarsMoveList({required super.name, required super.uuid, required this.commandMoves, super.deviceCategory = const [DeviceType.ears], super.actionCategory = ActionCategory.ears}); } @Riverpod(keepAlive: true) @@ -175,15 +174,13 @@ class MoveLists extends _$MoveLists { } Future add(MoveList moveList) async { - List state2 = List.from(state); - state2.add(moveList); + List state2 = List.from(state)..add(moveList); state = state2; await store(); } Future remove(MoveList moveList) async { - List state2 = List.from(state); - state2.remove(moveList); + List state2 = List.from(state)..remove(moveList); state = state2; await store(); } @@ -295,11 +292,19 @@ List generateMoveCommand(Move move, BaseStatefulDevice device, } } else if (move.moveType == MoveType.move) { if (device.baseDeviceDefinition.deviceType == DeviceType.ears) { - commands.add(BluetoothMessage(message: "SPEED ${move.speed > 60 ? Speed.fast.name.toUpperCase() : Speed.slow.name.toUpperCase()}", device: device, priority: Priority.normal, responseMSG: "SPEED ${move.speed > 60 ? Speed.fast.name.toUpperCase() : Speed.slow.name.toUpperCase()}", type: type)); - commands.add(BluetoothMessage(message: "DSSP ${move.leftServo.round().clamp(0, 128)} ${move.rightServo.round().clamp(0, 128)} 000 000", device: device, priority: Priority.normal, responseMSG: "DSSP END", type: CommandType.move)); + commands + ..add(BluetoothMessage(message: "SPEED ${move.speed > 60 ? Speed.fast.name.toUpperCase() : Speed.slow.name.toUpperCase()}", device: device, priority: Priority.normal, responseMSG: "SPEED ${move.speed > 60 ? Speed.fast.name.toUpperCase() : Speed.slow.name.toUpperCase()}", type: type)) + ..add(BluetoothMessage(message: "DSSP ${move.leftServo.round().clamp(0, 128)} ${move.rightServo.round().clamp(0, 128)} 000 000", device: device, priority: Priority.normal, responseMSG: "DSSP END", type: CommandType.move)); } else { - commands.add(BluetoothMessage( - message: "DSSP E${move.easingType.num} F${move.easingType.num} A${move.leftServo.round().clamp(0, 128) ~/ 16} B${move.rightServo.round().clamp(0, 128) ~/ 16} L${move.speed.toInt()} M${move.speed.toInt()}", device: device, priority: Priority.normal, responseMSG: "OK", type: type)); + commands.add( + BluetoothMessage( + message: "DSSP E${move.easingType.num} F${move.easingType.num} A${move.leftServo.round().clamp(0, 128) ~/ 16} B${move.rightServo.round().clamp(0, 128) ~/ 16} L${move.speed.toInt()} M${move.speed.toInt()}", + device: device, + priority: Priority.normal, + responseMSG: "OK", + type: type, + ), + ); } } return commands; diff --git a/lib/Backend/notifications.dart b/lib/Backend/notifications.dart index 47f2c2062..62e7595ac 100644 --- a/lib/Backend/notifications.dart +++ b/lib/Backend/notifications.dart @@ -1,21 +1,22 @@ import 'package:awesome_notifications/awesome_notifications.dart'; -import 'package:tail_app/Frontend/utils.dart'; +import '../Frontend/utils.dart'; import 'package:url_launcher/url_launcher.dart'; import '../constants.dart'; Future initNotifications() async { AwesomeNotifications().initialize( - // set the icon to null if you want to use the default app icon - 'resource://drawable/res_app_icon', - [ - NotificationChannel( - channelKey: blogChannelKey, - channelName: 'Tail Blog', - channelDescription: 'New Posts in the Tail Blog', - ) - ], - debug: true); + // set the icon to null if you want to use the default app icon + 'resource://drawable/res_app_icon', + [ + NotificationChannel( + channelKey: blogChannelKey, + channelName: 'Tail Blog', + channelDescription: 'New Posts in the Tail Blog', + ), + ], + debug: true, + ); } class NotificationController { diff --git a/lib/Backend/plausible_dio.dart b/lib/Backend/plausible_dio.dart index 7effc913b..985396e95 100644 --- a/lib/Backend/plausible_dio.dart +++ b/lib/Backend/plausible_dio.dart @@ -8,13 +8,13 @@ import 'package:install_referrer/install_referrer.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:plausible_analytics/plausible_analytics.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:tail_app/Backend/move_lists.dart'; import '../Frontend/utils.dart'; import '../constants.dart'; import '../main.dart'; import 'Definitions/Device/device_definition.dart'; import 'logging_wrappers.dart'; +import 'move_lists.dart'; import 'sensors.dart'; class PlausibleDio extends Plausible { @@ -66,8 +66,9 @@ class PlausibleDio extends Plausible { ), ); } catch (e) { - transaction.throwable = e; - transaction.status = const SpanStatus.internalError(); + transaction + ..throwable = e + ..status = const SpanStatus.internalError(); if (kDebugMode) { print(e); } diff --git a/lib/Backend/sensors.dart b/lib/Backend/sensors.dart index ad1e1f3b7..98599064f 100644 --- a/lib/Backend/sensors.dart +++ b/lib/Backend/sensors.dart @@ -1,3 +1,5 @@ +// ignore_for_file: cascade_invocations + import 'dart:async'; import 'dart:io'; import 'dart:math'; @@ -14,15 +16,15 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:proximity_sensor/proximity_sensor.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shake/shake.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager_plus.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_message.dart'; -import 'package:tail_app/Backend/action_registry.dart'; import '../Frontend/translation_string_definitions.dart'; import '../constants.dart'; import 'Bluetooth/bluetooth_manager.dart'; +import 'Bluetooth/bluetooth_manager_plus.dart'; +import 'Bluetooth/bluetooth_message.dart'; import 'Definitions/Action/base_action.dart'; import 'Definitions/Device/device_definition.dart'; +import 'action_registry.dart'; import 'logging_wrappers.dart'; import 'move_lists.dart'; @@ -159,19 +161,21 @@ abstract class TriggerDefinition extends ChangeNotifier implements Comparable allActionsMapped = triggerAction.actions.map((element) => ref.read(getActionFromUUIDProvider(element))).where( - // filter out missing actions - (element) => element != null).map( - // mark remaining not null - (e) => e!).toList(); + final List allActionsMapped = triggerAction.actions + .map((element) => ref.read(getActionFromUUIDProvider(element))) + .where( + // filter out missing actions + (element) => element != null, + ) + .map( + // mark remaining not null + (e) => e!, + ) + .toList(); // no moves exist if (allActionsMapped.isEmpty) { @@ -293,7 +303,7 @@ class WalkingTriggerDefinition extends TriggerDefinition { super.actionTypes = [ TriggerActionDef(name: "Walking", translated: triggerWalkingTitle(), uuid: "77d22961-5a69-465a-bd27-5cf5508d10a6"), TriggerActionDef(name: "Stopped", translated: triggerWalkingStopped(), uuid: "7424097d-ba24-4d85-b963-bf58e85e289d"), - TriggerActionDef(name: "Step", translated: triggerWalkingStep(), uuid: "c82b04ba-7d2e-475a-90ba-3d354e5b8ef0") + TriggerActionDef(name: "Step", translated: triggerWalkingStep(), uuid: "c82b04ba-7d2e-475a-90ba-3d354e5b8ef0"), ]; } @@ -610,10 +620,12 @@ class ShakeTriggerDefinition extends TriggerDefinition { if (detector != null) { return; } - detector = ShakeDetector.waitForStart(onPhoneShake: () { - sensorsLogger.fine("Shake Detected"); - sendCommands("Shake", ref); - }); + detector = ShakeDetector.waitForStart( + onPhoneShake: () { + sensorsLogger.fine("Shake Detected"); + sendCommands("Shake", ref); + }, + ); detector?.startListening(); } } @@ -756,22 +768,21 @@ class TriggerList extends _$TriggerList { trigger.actions.firstWhere((element) => element.uuid == '7424097d-ba24-4d85-b963-bf58e85e289d').actions.add(ActionRegistry.allCommands.firstWhere((element) => element.uuid == '86b13d13-b09c-46ba-a887-b40d8118b00a').uuid); trigger.actions.firstWhere((element) => element.uuid == '7424097d-ba24-4d85-b963-bf58e85e289d').actions.add(ActionRegistry.allCommands.firstWhere((element) => element.uuid == 'd8384bcf-31ed-4b5d-a25a-da3a2f96e406').uuid); - HiveProxy.clear(triggerBox); - HiveProxy.addAll(triggerBox, [trigger]); + unawaited(store()); return [trigger]; } return results; } - void add(Trigger trigger) { + Future add(Trigger trigger) async { state.add(trigger); - store(); + await store(); } - void remove(Trigger trigger) { + Future remove(Trigger trigger) async { trigger.enabled = false; state.remove(trigger); - store(); + await store(); } Future store() async { diff --git a/lib/Backend/wear_bridge.dart b/lib/Backend/wear_bridge.dart index be19f3db9..423c72da0 100644 --- a/lib/Backend/wear_bridge.dart +++ b/lib/Backend/wear_bridge.dart @@ -2,18 +2,19 @@ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; +import 'package:collection/collection.dart'; import 'package:cross_platform/cross_platform.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_wear_os_connectivity/flutter_wear_os_connectivity.dart'; import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:tail_app/Backend/sensors.dart'; import 'Bluetooth/bluetooth_manager.dart'; import 'Definitions/Action/base_action.dart'; import 'Definitions/Device/device_definition.dart'; import 'favorite_actions.dart'; import 'move_lists.dart'; +import 'sensors.dart'; part 'wear_bridge.g.dart'; @@ -66,9 +67,11 @@ Future initWear(InitWearRef ref) async { _messagereceivedStreamSubscription = _flutterWearOsConnectivity.messageReceived().listen( (message) => _wearLogger.info("messageReceived $message"), ); - capabilityChangedStreamSubscription = _flutterWearOsConnectivity.capabilityChanged(capabilityPathURI: Uri(scheme: "wear", host: "*", path: "/*")).listen((event) => _wearLogger.info( - "capabilityChanged $event", - )); + capabilityChangedStreamSubscription = _flutterWearOsConnectivity.capabilityChanged(capabilityPathURI: Uri(scheme: "wear", host: "*", path: "/*")).listen( + (event) => _wearLogger.info( + "capabilityChanged $event", + ), + ); updateWearActions(ref.read(favoriteActionsProvider), ref); } catch (e, s) { _wearLogger.severe("exception setting up Wear $e", e, s); @@ -77,9 +80,11 @@ Future initWear(InitWearRef ref) async { Future updateWearActions(List favoriteActions, Ref ref) async { try { - Iterable allActions = favoriteActions.map( - (e) => ref.read(getActionFromUUIDProvider(e.actionUUID)) as BaseAction, - ); + Iterable allActions = favoriteActions + .map( + (e) => ref.read(getActionFromUUIDProvider(e.actionUUID)), + ) + .whereNotNull(); final Map favoriteMap = Map.fromEntries(allActions.map((e) => MapEntry(e.uuid, e.name))); final Map map = Map.fromEntries( [ diff --git a/lib/Frontend/Widgets/base_card.dart b/lib/Frontend/Widgets/base_card.dart index d0c3d6a27..b0ac1f621 100644 --- a/lib/Frontend/Widgets/base_card.dart +++ b/lib/Frontend/Widgets/base_card.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; class BaseCard extends StatelessWidget { - const BaseCard({super.key, required this.child, this.elevation = 1, this.color}); + const BaseCard({required this.child, super.key, this.elevation = 1, this.color}); final double elevation; final Widget child; diff --git a/lib/Frontend/Widgets/bt_app_state_controller.dart b/lib/Frontend/Widgets/bt_app_state_controller.dart index 58362f207..e1e900065 100644 --- a/lib/Frontend/Widgets/bt_app_state_controller.dart +++ b/lib/Frontend/Widgets/bt_app_state_controller.dart @@ -6,7 +6,7 @@ import '../../Backend/logging_wrappers.dart'; import '../../constants.dart'; class BtAppStateController extends ConsumerStatefulWidget { - const BtAppStateController({super.key, required this.child}); + const BtAppStateController({required this.child, super.key}); final Widget child; diff --git a/lib/Frontend/Widgets/device_type_widget.dart b/lib/Frontend/Widgets/device_type_widget.dart index 253080c7f..e794f06f3 100644 --- a/lib/Frontend/Widgets/device_type_widget.dart +++ b/lib/Frontend/Widgets/device_type_widget.dart @@ -1,13 +1,13 @@ import 'package:choice/choice.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart'; +import '../../Backend/Bluetooth/bluetooth_manager.dart'; import '../../Backend/Definitions/Device/device_definition.dart'; import '../translation_string_definitions.dart'; class DeviceTypeWidget extends ConsumerWidget { - const DeviceTypeWidget({super.key, required this.selected, required this.onSelectionChanged}); + const DeviceTypeWidget({required this.selected, required this.onSelectionChanged, super.key}); final List selected; final Function(List value) onSelectionChanged; diff --git a/lib/Frontend/Widgets/known_gear.dart b/lib/Frontend/Widgets/known_gear.dart index 8415adbd9..d6451f24b 100644 --- a/lib/Frontend/Widgets/known_gear.dart +++ b/lib/Frontend/Widgets/known_gear.dart @@ -1,16 +1,18 @@ +import 'dart:async'; + import 'package:animate_do/animate_do.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; -import 'package:tail_app/Frontend/Widgets/scan_for_new_device.dart'; -import 'package:tail_app/Frontend/utils.dart'; import '../../Backend/Bluetooth/bluetooth_manager.dart'; +import '../../Backend/Definitions/Device/device_definition.dart'; import '../../Backend/logging_wrappers.dart'; import '../../constants.dart'; import '../../main.dart'; import '../pages/shell.dart'; import '../translation_string_definitions.dart'; +import '../utils.dart'; +import 'scan_for_new_device.dart'; class KnownGear extends ConsumerStatefulWidget { const KnownGear({super.key}); @@ -22,8 +24,7 @@ class KnownGear extends ConsumerStatefulWidget { class _KnownGearState extends ConsumerState { @override Widget build(BuildContext context) { - List knownDevices = ref.watch(knownDevicesProvider).values.toList(); - knownDevices.sort((a, b) => a.deviceConnectionState.value.index.compareTo(b.deviceConnectionState.value.index)); + List knownDevices = ref.watch(knownDevicesProvider).values.toList()..sort((a, b) => a.deviceConnectionState.value.index.compareTo(b.deviceConnectionState.value.index)); return Row( mainAxisSize: MainAxisSize.max, children: [ @@ -64,9 +65,9 @@ class ScanForNewGearButton extends ConsumerWidget { ), ), ), - onTap: () { + onTap: () async { plausible.event(page: "Scan For New Gear"); - showModalBottomSheet( + await showModalBottomSheet( context: context, showDragHandle: true, isScrollControlled: true, @@ -105,7 +106,7 @@ class ScanForNewGearButton extends ConsumerWidget { } class KnownGearCard extends ConsumerStatefulWidget { - const KnownGearCard({super.key, required this.baseStatefulDevice}); + const KnownGearCard({required this.baseStatefulDevice, super.key}); final BaseStatefulDevice baseStatefulDevice; @@ -143,8 +144,8 @@ class _KnownGearCardState extends ConsumerState { clipBehavior: Clip.antiAlias, color: cardColor, child: InkWell( - onTap: () { - plausible.event(page: "Manage Gear"); + onTap: () async { + unawaited(plausible.event(page: "Manage Gear")); showModalBottomSheet( context: context, showDragHandle: true, @@ -237,7 +238,7 @@ class _KnownGearCardState extends ConsumerState { duration: animationTransitionDuration, ), ), - ) + ), ], ), ), diff --git a/lib/Frontend/Widgets/known_gear_scan_controller.dart b/lib/Frontend/Widgets/known_gear_scan_controller.dart index 53fd67deb..5af9a9ee7 100644 --- a/lib/Frontend/Widgets/known_gear_scan_controller.dart +++ b/lib/Frontend/Widgets/known_gear_scan_controller.dart @@ -1,19 +1,21 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_flutter/adapters.dart'; import 'package:logging/logging.dart' as log; import 'package:multi_value_listenable_builder/multi_value_listenable_builder.dart'; import 'package:sentry_hive/sentry_hive.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager_plus.dart'; -import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; -import 'package:tail_app/constants.dart'; import '../../Backend/Bluetooth/bluetooth_manager.dart'; +import '../../Backend/Bluetooth/bluetooth_manager_plus.dart'; +import '../../Backend/Definitions/Device/device_definition.dart'; +import '../../constants.dart'; final knownGearScanControllerLogger = log.Logger('KnownGearScanController'); class KnownGearScanController extends ConsumerStatefulWidget { - const KnownGearScanController({super.key, required this.child}); + const KnownGearScanController({required this.child, super.key}); final Widget child; @@ -36,9 +38,9 @@ class _KnownGearScanControllerState extends ConsumerState _handleTransition('resume'), - onHide: () { + onHide: () async { if (!isAnyGearConnected.value) { - stopScan(); + await stopScan(); } }, //onInactive: () => _handleTransition('inactive'), @@ -52,7 +54,7 @@ class _KnownGearScanControllerState extends ConsumerState( - onNotification: (OverscrollNotification notification) { - //knownGearScanControllerLogger.info('Overscroll ${notification.overscroll}'); - if (notification.overscroll < 2 && notification.overscroll > -2) { - // ignore, don't do anything - return false; - } - if (!alwaysScan) { - beginScan(timeout: scanDurationTimeout); - } - return true; - }, - child: widget.child), + onNotification: (OverscrollNotification notification) { + //knownGearScanControllerLogger.info('Overscroll ${notification.overscroll}'); + if (notification.overscroll < 2 && notification.overscroll > -2) { + // ignore, don't do anything + return false; + } + if (!alwaysScan) { + unawaited(beginScan(timeout: scanDurationTimeout)); + } + return true; + }, + child: widget.child, + ), ], ), ); @@ -115,7 +118,7 @@ class _KnownGearScanControllerState extends ConsumerState { detector = ShakeDetector.waitForStart( onPhoneShake: () { if (context.mounted) { - context.push("/settings/developer/logs"); + unawaited(context.push("/settings/developer/logs")); } else { detector?.stopListening(); detector = null; diff --git a/lib/Frontend/Widgets/lottie_lazy_load.dart b/lib/Frontend/Widgets/lottie_lazy_load.dart index e9b0899d9..64bb39a3a 100644 --- a/lib/Frontend/Widgets/lottie_lazy_load.dart +++ b/lib/Frontend/Widgets/lottie_lazy_load.dart @@ -4,7 +4,7 @@ import 'package:lottie_native/lottie_native.dart'; import '../../constants.dart'; class LottieLazyLoad extends StatefulWidget { - const LottieLazyLoad({super.key, required this.asset, required this.width}); + const LottieLazyLoad({required this.asset, required this.width, super.key}); final String asset; final double width; diff --git a/lib/Frontend/Widgets/scan_for_new_device.dart b/lib/Frontend/Widgets/scan_for_new_device.dart index 37db5c423..cd23c87c0 100644 --- a/lib/Frontend/Widgets/scan_for_new_device.dart +++ b/lib/Frontend/Widgets/scan_for_new_device.dart @@ -1,13 +1,14 @@ +import 'dart:async'; + import 'package:animate_do/animate_do.dart'; import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager_plus.dart'; -import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; -import 'package:tail_app/Frontend/Widgets/tutorial_card.dart'; +import '../../Backend/Bluetooth/bluetooth_manager.dart'; +import '../../Backend/Bluetooth/bluetooth_manager_plus.dart'; +import '../../Backend/Definitions/Device/device_definition.dart'; import '../../Backend/device_registry.dart'; import '../../Backend/logging_wrappers.dart'; import '../../constants.dart'; @@ -15,9 +16,10 @@ import '../../gen/assets.gen.dart'; import '../../main.dart'; import '../translation_string_definitions.dart'; import 'lottie_lazy_load.dart'; +import 'tutorial_card.dart'; class ScanForNewDevice extends ConsumerStatefulWidget { - const ScanForNewDevice({super.key, required this.scrollController}); + const ScanForNewDevice({required this.scrollController, super.key}); final ScrollController scrollController; @@ -30,7 +32,7 @@ class _ScanForNewDevice extends ConsumerState { @override void initState() { - beginScan(); + unawaited(beginScan()); super.initState(); anyKnownGear = ref.read(knownDevicesProvider).isNotEmpty; } @@ -39,7 +41,7 @@ class _ScanForNewDevice extends ConsumerState { void dispose() { super.dispose(); if (!HiveProxy.getOrDefault(settings, alwaysScanning, defaultValue: alwaysScanningDefault) || !anyKnownGear) { - stopScan(); + unawaited(stopScan()); } } @@ -52,73 +54,76 @@ class _ScanForNewDevice extends ConsumerState { valueListenable: isBluetoothEnabled, builder: (BuildContext context, bool value, Widget? child) { if (value) { - return ListView(controller: widget.scrollController, children: [ - StreamBuilder>( - stream: flutterBluePlus.scanResults, - builder: (BuildContext context, AsyncSnapshot> snapshot) { - List list = []; - if (snapshot.hasData) { - list = snapshot.data!.where((test) => !knownDeviceIds.contains(test.device.remoteId.str)).toList(); - anyGearFound = list.isNotEmpty; - } - return ListView( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - children: [ - AnimatedCrossFade( - firstChild: anyGearFound - ? ListView( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - children: [ - ListTile( - title: Text( - scanDevicesFoundTitle(), - style: Theme.of(context).textTheme.titleMedium, + return ListView( + controller: widget.scrollController, + children: [ + StreamBuilder>( + stream: flutterBluePlus.scanResults, + builder: (BuildContext context, AsyncSnapshot> snapshot) { + List list = []; + if (snapshot.hasData) { + list = snapshot.data!.where((test) => !knownDeviceIds.contains(test.device.remoteId.str)).toList(); + anyGearFound = list.isNotEmpty; + } + return ListView( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + children: [ + AnimatedCrossFade( + firstChild: anyGearFound + ? ListView( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + children: [ + ListTile( + title: Text( + scanDevicesFoundTitle(), + style: Theme.of(context).textTheme.titleMedium, + ), ), - ), - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: list.length, - itemBuilder: (BuildContext context, int index) { - ScanResult e = list[index]; - return ListTile( - title: Text(getNameFromBTName(e.device.advName)), - trailing: Text(HiveProxy.getOrDefault(settings, showDebugging, defaultValue: showDebuggingDefault) ? e.device.remoteId.str : ""), - onTap: () async { - await e.device.connect(); - plausible.event(name: "Connect New Gear", props: {"Gear Type": e.device.advName}); - if (context.mounted) { - Navigator.pop(context); - } - }, - ); - }, - ), - if (HiveProxy.getOrDefault(settings, showDebugging, defaultValue: showDebuggingDefault)) ...[ - Center( - child: FilledButton( - onPressed: () { + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: list.length, + itemBuilder: (BuildContext context, int index) { + ScanResult e = list[index]; + return ListTile( + title: Text(getNameFromBTName(e.device.advName)), + trailing: Text(HiveProxy.getOrDefault(settings, showDebugging, defaultValue: showDebuggingDefault) ? e.device.remoteId.str : ""), + onTap: () async { + await e.device.connect(); + plausible.event(name: "Connect New Gear", props: {"Gear Type": e.device.advName}); + if (context.mounted) { + Navigator.pop(context); + } + }, + ); + }, + ), + if (HiveProxy.getOrDefault(settings, showDebugging, defaultValue: showDebuggingDefault)) ...[ + Center( + child: FilledButton( + onPressed: () async { for (ScanResult scanResult in list) { scanResult.device.connect(); } Navigator.pop(context); }, - child: const Text('Connect to all')), - ) - ] - ], - ) - : Container(), - secondChild: Container(), - crossFadeState: anyGearFound ? CrossFadeState.showFirst : CrossFadeState.showSecond, - duration: animationTransitionDuration, - ), - AnimatedOpacity( - opacity: anyGearFound ? 0.5 : 1, - duration: animationTransitionDuration, - child: Padding( + child: const Text('Connect to all'), + ), + ), + ], + ], + ) + : Container(), + secondChild: Container(), + crossFadeState: anyGearFound ? CrossFadeState.showFirst : CrossFadeState.showSecond, + duration: animationTransitionDuration, + ), + AnimatedOpacity( + opacity: anyGearFound ? 0.5 : 1, + duration: animationTransitionDuration, + child: Padding( padding: const EdgeInsets.only(top: 20), child: Center( child: Column( @@ -137,72 +142,73 @@ class _ScanForNewDevice extends ConsumerState { Padding( padding: const EdgeInsets.all(16.0), child: Text(scanDevicesScanMessage()), - ) + ), ], ), - )), + ), + ), + ), + ], + ); + }, + ), + if (HiveProxy.getOrDefault(settings, showDemoGear, defaultValue: showDemoGearDefault)) ...[ + ExpansionTile( + title: Text(scanDemoGear()), + children: [ + PageInfoCard( + text: scanDemoGearTip(), ), - ], - ); - }, - ), - if (HiveProxy.getOrDefault(settings, showDemoGear, defaultValue: showDemoGearDefault)) ...[ - ExpansionTile( - title: Text(scanDemoGear()), - children: [ - PageInfoCard( - text: scanDemoGearTip(), - ), - ListTile( - leading: const Icon(Icons.add), - subtitle: DropdownMenu( - initialSelection: null, - expandedInsets: EdgeInsets.zero, - label: Text(scanAddDemoGear()), - onSelected: (value) { - if (value != null) { - setState( - () { - BaseStoredDevice baseStoredDevice; - BaseStatefulDevice statefulDevice; - baseStoredDevice = BaseStoredDevice(value.uuid, "DEV${value.deviceType.name}", value.deviceType.color(ref: ref).value); - baseStoredDevice.name = getNameFromBTName(value.btName); - statefulDevice = BaseStatefulDevice(value, baseStoredDevice); - statefulDevice.deviceConnectionState.value = ConnectivityState.connected; - isAnyGearConnected.value = true; - if (!ref.read(knownDevicesProvider).containsKey(baseStoredDevice.btMACAddress)) { - ref.read(knownDevicesProvider.notifier).add(statefulDevice); - } - context.pop(); - }, - ); + ListTile( + leading: const Icon(Icons.add), + subtitle: DropdownMenu( + initialSelection: null, + expandedInsets: EdgeInsets.zero, + label: Text(scanAddDemoGear()), + onSelected: (value) async { + if (value != null) { + setState( + () { + BaseStoredDevice baseStoredDevice; + BaseStatefulDevice statefulDevice; + baseStoredDevice = BaseStoredDevice(value.uuid, "DEV${value.deviceType.name}", value.deviceType.color(ref: ref).value)..name = getNameFromBTName(value.btName); + statefulDevice = BaseStatefulDevice(value, baseStoredDevice); + statefulDevice.deviceConnectionState.value = ConnectivityState.connected; + isAnyGearConnected.value = true; + if (!ref.read(knownDevicesProvider).containsKey(baseStoredDevice.btMACAddress)) { + ref.read(knownDevicesProvider.notifier).add(statefulDevice); + } + context.pop(); + }, + ); + } + }, + dropdownMenuEntries: DeviceRegistry.allDevices.map((e) => DropdownMenuEntry(value: e, label: getNameFromBTName(e.btName))).toList(), + ), + ), + ListTile( + title: Text(scanRemoveDemoGear()), + leading: const Icon(Icons.delete), + onTap: () async { + ref.read(knownDevicesProvider).removeWhere((key, value) => key.contains("DEV")); + ref.read(knownDevicesProvider.notifier).remove(""); // force update + if (ref + .read(knownDevicesProvider) + .values + .where( + (element) => element.deviceConnectionState.value == ConnectivityState.connected, + ) + .isEmpty) { + isAnyGearConnected.value = false; } + context.pop(); }, - dropdownMenuEntries: DeviceRegistry.allDevices.map((e) => DropdownMenuEntry(value: e, label: getNameFromBTName(e.btName))).toList(), ), - ), - ListTile( - title: Text(scanRemoveDemoGear()), - leading: const Icon(Icons.delete), - onTap: () { - ref.read(knownDevicesProvider).removeWhere((key, value) => key.contains("DEV")); - ref.read(knownDevicesProvider.notifier).remove(""); // force update - if (ref - .read(knownDevicesProvider) - .values - .where( - (element) => element.deviceConnectionState.value == ConnectivityState.connected, - ) - .isEmpty) { - isAnyGearConnected.value = false; - } - context.pop(); - }, - ), - ], - ), - ] - ]); + ], + ), + ], + ], + ); } else { return Center( child: Text(actionsNoBluetooth()), //TODO: More detail diff --git a/lib/Frontend/Widgets/snack_bar_overlay.dart b/lib/Frontend/Widgets/snack_bar_overlay.dart index d3c70cbc3..65019e1eb 100644 --- a/lib/Frontend/Widgets/snack_bar_overlay.dart +++ b/lib/Frontend/Widgets/snack_bar_overlay.dart @@ -6,9 +6,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:multi_value_listenable_builder/multi_value_listenable_builder.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart'; -import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; +import '../../Backend/Bluetooth/bluetooth_manager.dart'; +import '../../Backend/Definitions/Device/device_definition.dart'; import '../translation_string_definitions.dart'; part 'snack_bar_overlay.g.dart'; @@ -24,7 +24,7 @@ class SnackbarStream extends _$SnackbarStream { } class SnackBarOverlay extends ConsumerWidget { - const SnackBarOverlay({super.key, required this.child}); + const SnackBarOverlay({required this.child, super.key}); final Widget child; @@ -100,7 +100,7 @@ class SnackBarOverlay extends ConsumerWidget { content: AwesomeSnackbarContent( title: otaAvailableSnackbarTitle(), message: otaAvailableSnackbarLabel(), - onPressed: () { + onPressed: () async { ScaffoldMessenger.of(context).clearSnackBars(); return context.push("/ota", extra: baseStatefulDevice.baseStoredDevice.btMACAddress); }, diff --git a/lib/Frontend/Widgets/speed_widget.dart b/lib/Frontend/Widgets/speed_widget.dart index 352c17edf..8bd129cbc 100644 --- a/lib/Frontend/Widgets/speed_widget.dart +++ b/lib/Frontend/Widgets/speed_widget.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import '../translation_string_definitions.dart'; class SpeedWidget extends StatelessWidget { - const SpeedWidget({super.key, required this.value, required this.onChanged}); + const SpeedWidget({required this.value, required this.onChanged, super.key}); final Function(double value) onChanged; final double value; diff --git a/lib/Frontend/Widgets/tail_blog.dart b/lib/Frontend/Widgets/tail_blog.dart index 5ed80ab7f..b4df6a7d6 100644 --- a/lib/Frontend/Widgets/tail_blog.dart +++ b/lib/Frontend/Widgets/tail_blog.dart @@ -1,19 +1,20 @@ +import 'dart:async'; import 'dart:typed_data'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; -import 'package:tail_app/Frontend/utils.dart'; -import 'package:tail_app/constants.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:wordpress_client/wordpress_client.dart'; import '../../Backend/logging_wrappers.dart'; +import '../../constants.dart'; +import '../utils.dart'; final _wpLogger = Logger('Main'); class TailBlog extends StatefulWidget { - const TailBlog({super.key, required this.controller}); + const TailBlog({required this.controller, super.key}); final ScrollController controller; @@ -68,7 +69,7 @@ class _TailBlogState extends State { child: snapshot.hasData ? snapshot.data! : const CircularProgressIndicator(), ); }, - ) + ), ], Card( clipBehavior: Clip.antiAlias, @@ -104,7 +105,7 @@ class _TailBlogState extends State { @override void initState() { super.initState(); - getFeed(); + unawaited(getFeed()); } Future getFeed() async { @@ -133,14 +134,16 @@ class _TailBlogState extends State { if (_wordpressPosts.isNotEmpty) { for (Post post in _wordpressPosts) { - results.add(FeedItem( - title: post.title!.parsedText, - publishDate: post.date!, - url: post.link, - feedType: FeedType.blog, - imageId: post.featuredMedia, - imageUrl: post.featuredImageUrl, - )); + results.add( + FeedItem( + title: post.title!.parsedText, + publishDate: post.date!, + url: post.link, + feedType: FeedType.blog, + imageId: post.featuredMedia, + imageUrl: post.featuredImageUrl, + ), + ); } } if (results.isNotEmpty && context.mounted) { diff --git a/lib/Frontend/Widgets/tutorial_card.dart b/lib/Frontend/Widgets/tutorial_card.dart index 32feca6d1..e172019c4 100644 --- a/lib/Frontend/Widgets/tutorial_card.dart +++ b/lib/Frontend/Widgets/tutorial_card.dart @@ -2,18 +2,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:multi_value_listenable_builder/multi_value_listenable_builder.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart'; -import 'package:tail_app/Frontend/Widgets/base_card.dart'; +import '../../Backend/Bluetooth/bluetooth_manager.dart'; import '../../Backend/logging_wrappers.dart'; import '../../constants.dart'; import '../translation_string_definitions.dart'; import '../utils.dart'; +import 'base_card.dart'; class PageInfoCard extends StatelessWidget { final String text; - const PageInfoCard({super.key, required this.text}); + const PageInfoCard({required this.text, super.key}); @override Widget build(BuildContext context) { @@ -52,32 +52,33 @@ class GearOutOfDateWarning extends ConsumerWidget { if (values.contains(true)) { Color color = Theme.of(context).colorScheme.primary; return BaseCard( - color: color, - child: InkWell( - onTap: () { - String? mac = ref - .read(knownDevicesProvider) - .values - .where( - (element) => element.mandatoryOtaRequired.value, - ) - .firstOrNull - ?.baseStoredDevice - .btMACAddress; - if (mac != null) { - context.push("/ota", extra: mac); - } - }, - child: Center( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Text( - featureLimitedOtaRequiredLabel(), - style: Theme.of(context).textTheme.labelLarge!.copyWith(color: getTextColor(color)), - ), + color: color, + child: InkWell( + onTap: () async { + String? mac = ref + .read(knownDevicesProvider) + .values + .where( + (element) => element.mandatoryOtaRequired.value, + ) + .firstOrNull + ?.baseStoredDevice + .btMACAddress; + if (mac != null) { + context.push("/ota", extra: mac); + } + }, + child: Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + featureLimitedOtaRequiredLabel(), + style: Theme.of(context).textTheme.labelLarge!.copyWith(color: getTextColor(color)), ), ), - )); + ), + ), + ); } else { return Container(); } diff --git a/lib/Frontend/go_router_config.dart b/lib/Frontend/go_router_config.dart index 142d8d42f..c1064caa5 100644 --- a/lib/Frontend/go_router_config.dart +++ b/lib/Frontend/go_router_config.dart @@ -4,23 +4,23 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:logarte/logarte.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; -import 'package:tail_app/Frontend/pages/action_selector.dart'; -import 'package:tail_app/Frontend/pages/custom_audio.dart'; -import 'package:tail_app/Frontend/pages/developer/bluetooth_console.dart'; -import 'package:tail_app/Frontend/pages/developer/developer_menu.dart'; -import 'package:tail_app/Frontend/pages/developer/developer_pincode.dart'; -import 'package:tail_app/Frontend/pages/direct_gear_control.dart'; -import 'package:tail_app/Frontend/pages/intro.dart'; -import 'package:tail_app/Frontend/pages/markdown_viewer.dart'; -import 'package:tail_app/Frontend/pages/more.dart'; -import 'package:tail_app/Frontend/pages/move_list.dart'; -import 'package:tail_app/Frontend/pages/ota_update.dart'; -import 'package:tail_app/Frontend/pages/settings.dart'; -import 'package:tail_app/Frontend/pages/shell.dart'; -import 'package:tail_app/Frontend/pages/triggers.dart'; -import 'package:tail_app/Frontend/pages/view_pdf.dart'; -import 'package:tail_app/constants.dart'; +import '../Backend/Definitions/Device/device_definition.dart'; +import 'pages/action_selector.dart'; +import 'pages/custom_audio.dart'; +import 'pages/developer/bluetooth_console.dart'; +import 'pages/developer/developer_menu.dart'; +import 'pages/developer/developer_pincode.dart'; +import 'pages/direct_gear_control.dart'; +import 'pages/intro.dart'; +import 'pages/markdown_viewer.dart'; +import 'pages/more.dart'; +import 'pages/move_list.dart'; +import 'pages/ota_update.dart'; +import 'pages/settings.dart'; +import 'pages/shell.dart'; +import 'pages/triggers.dart'; +import 'pages/view_pdf.dart'; +import '../constants.dart'; import '../Backend/NavigationObserver/custom_go_router_navigation_observer.dart'; import '../Backend/logging_wrappers.dart'; @@ -45,43 +45,45 @@ final GoRouter router = GoRouter( observers: [SentryNavigatorObserver(), CustomNavObserver(plausible)], routes: [ GoRoute( + name: 'Actions', + path: '/', + parentNavigatorKey: _shellNavigatorKey, + pageBuilder: (BuildContext context, GoRouterState state) => NoTransitionPage( + child: const ActionPage(), + key: state.pageKey, name: 'Actions', - path: '/', - parentNavigatorKey: _shellNavigatorKey, - pageBuilder: (BuildContext context, GoRouterState state) => NoTransitionPage( - child: const ActionPage(), - key: state.pageKey, - name: 'Actions', - ), - redirect: (context, state) { - if (HiveProxy.getOrDefault(settings, hasCompletedOnboarding, defaultValue: hasCompletedOnboardingDefault) < hasCompletedOnboardingVersionToAgree) { - return '/onboarding'; - } - return null; - }), + ), + redirect: (context, state) { + if (HiveProxy.getOrDefault(settings, hasCompletedOnboarding, defaultValue: hasCompletedOnboardingDefault) < hasCompletedOnboardingVersionToAgree) { + return '/onboarding'; + } + return null; + }, + ), GoRoute( + name: 'Triggers', + path: '/triggers', + parentNavigatorKey: _shellNavigatorKey, + pageBuilder: (BuildContext context, GoRouterState state) => NoTransitionPage( + child: const Triggers(), + key: state.pageKey, name: 'Triggers', - path: '/triggers', - parentNavigatorKey: _shellNavigatorKey, - pageBuilder: (BuildContext context, GoRouterState state) => NoTransitionPage( - child: const Triggers(), - key: state.pageKey, - name: 'Triggers', + ), + routes: [ + GoRoute( + name: 'Triggers/Select Action', + path: 'select', + parentNavigatorKey: _rootNavigatorKey, + pageBuilder: (BuildContext context, GoRouterState state) => MaterialPage( + child: ActionSelector( + actionSelectorInfo: state.extra! as ActionSelectorInfo, ), - routes: [ - GoRoute( + key: state.pageKey, name: 'Triggers/Select Action', - path: 'select', - parentNavigatorKey: _rootNavigatorKey, - pageBuilder: (BuildContext context, GoRouterState state) => MaterialPage( - child: ActionSelector( - actionSelectorInfo: state.extra! as ActionSelectorInfo, - ), - key: state.pageKey, - name: 'Triggers/Select Action', - ), ), - ]), + ), + ], + ), GoRoute( name: 'More', path: '/more', @@ -237,7 +239,7 @@ final GoRouter router = GoRouter( path: 'pin', parentNavigatorKey: _rootNavigatorKey, builder: (BuildContext context, GoRouterState state) => const DeveloperPincode(), - ) + ), ], ), ], diff --git a/lib/Frontend/pages/action_selector.dart b/lib/Frontend/pages/action_selector.dart index a7e061ef4..d0b9200db 100644 --- a/lib/Frontend/pages/action_selector.dart +++ b/lib/Frontend/pages/action_selector.dart @@ -2,10 +2,10 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart'; -import 'package:tail_app/Frontend/Widgets/tutorial_card.dart'; -import 'package:tail_app/Frontend/utils.dart'; -import 'package:tail_app/constants.dart'; +import '../../Backend/Bluetooth/bluetooth_manager.dart'; +import '../Widgets/tutorial_card.dart'; +import '../utils.dart'; +import '../../constants.dart'; import '../../Backend/Definitions/Action/base_action.dart'; import '../../Backend/Definitions/Device/device_definition.dart'; @@ -20,7 +20,7 @@ class ActionSelectorInfo { } class ActionSelector extends ConsumerStatefulWidget { - const ActionSelector({super.key, required this.actionSelectorInfo}); + const ActionSelector({required this.actionSelectorInfo, super.key}); final ActionSelectorInfo actionSelectorInfo; @@ -78,34 +78,34 @@ class _ActionSelectorState extends ConsumerState { @override Widget build(BuildContext context) { return Scaffold( - primary: true, - appBar: AppBar( - title: Text(actionsSelectScreen()), - actions: [ - IconButton( - onPressed: () { - setState(() { - selected = actionsCatMap.values.flattened.toList(); - }); - }, - icon: const Icon(Icons.select_all), - tooltip: triggersSelectAllLabel(), - ), - IconButton( - onPressed: () { - setState(() { - selected.clear(); - }); - }, - icon: const Icon(Icons.deselect), - tooltip: triggersSelectClearLabel(), - ) - ], - ), - extendBody: true, - bottomNavigationBar: Container( - decoration: BoxDecoration( - gradient: LinearGradient( + primary: true, + appBar: AppBar( + title: Text(actionsSelectScreen()), + actions: [ + IconButton( + onPressed: () { + setState(() { + selected = actionsCatMap.values.flattened.toList(); + }); + }, + icon: const Icon(Icons.select_all), + tooltip: triggersSelectAllLabel(), + ), + IconButton( + onPressed: () { + setState(() { + selected.clear(); + }); + }, + icon: const Icon(Icons.deselect), + tooltip: triggersSelectClearLabel(), + ), + ], + ), + extendBody: true, + bottomNavigationBar: Container( + decoration: BoxDecoration( + gradient: LinearGradient( colors: [ Colors.transparent, Theme.of(context).colorScheme.primary.withAlpha(128), @@ -113,94 +113,97 @@ class _ActionSelectorState extends ConsumerState { begin: Alignment.topCenter, end: Alignment.bottomCenter, tileMode: TileMode.clamp, - )), - child: ButtonBar( - alignment: MainAxisAlignment.center, - children: [ - FilledButton( - onPressed: () { - setState(() { - if (selected.isEmpty) { - context.pop(true); - } else { - context.pop(selected); - } - }); - }, - child: Row( - children: [ - Icon( - Icons.save, - color: getTextColor( - Theme.of(context).colorScheme.primary, - ), - ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 4), - ), - Text( - triggersSelectSaveLabel(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: getTextColor( - Theme.of(context).colorScheme.primary, - ), - ), - ) - ], - ), - ) - ], ), ), - body: ListView( - primary: true, + child: ButtonBar( + alignment: MainAxisAlignment.center, children: [ - PageInfoCard(text: triggerActionSelectorTutorialLabel()), - ListView.builder( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: catList.length, - itemBuilder: (BuildContext context, int categoryIndex) { - List actionsForCat = actionsCatMap.values.toList()[categoryIndex].toList(); - bool hasConnectedDevice = actionsForCat.map((e) => e.deviceCategory).flattened.toSet().intersection(knownDeviceTypes).isNotEmpty; - return Theme( - data: Theme.of(context).copyWith(dividerColor: Colors.transparent), - child: ExpansionTile( - initiallyExpanded: hasConnectedDevice, - title: Text( - catList[categoryIndex].friendly, - style: Theme.of(context).textTheme.titleLarge, + FilledButton( + onPressed: () { + setState(() { + if (selected.isEmpty) { + context.pop(true); + } else { + context.pop(selected); + } + }); + }, + child: Row( + children: [ + Icon( + Icons.save, + color: getTextColor( + Theme.of(context).colorScheme.primary, ), - children: [ - GridView.builder( - gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 125), - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: actionsForCat.length, - itemBuilder: (BuildContext context, int actionIndex) { - BaseAction baseAction = actionsForCat[actionIndex]; - bool isSelected = selected.contains(baseAction); - return TweenAnimationBuilder( - builder: (context, value, child) { - Color? color = Color.lerp(Theme.of(context).colorScheme.primary, Theme.of(context).cardColor, value); - return Card( - clipBehavior: Clip.antiAlias, - elevation: 2, - color: color, - child: cardChild(isSelected, baseAction, color!), - ); - }, - tween: isSelected ? Tween(begin: 1, end: 0) : Tween(begin: 0, end: 1), - duration: animationTransitionDuration, - ); - }) - ], ), - ); - }, + const Padding( + padding: EdgeInsets.symmetric(horizontal: 4), + ), + Text( + triggersSelectSaveLabel(), + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: getTextColor( + Theme.of(context).colorScheme.primary, + ), + ), + ), + ], + ), ), ], - )); + ), + ), + body: ListView( + primary: true, + children: [ + PageInfoCard(text: triggerActionSelectorTutorialLabel()), + ListView.builder( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: catList.length, + itemBuilder: (BuildContext context, int categoryIndex) { + List actionsForCat = actionsCatMap.values.toList()[categoryIndex].toList(); + bool hasConnectedDevice = actionsForCat.map((e) => e.deviceCategory).flattened.toSet().intersection(knownDeviceTypes).isNotEmpty; + return Theme( + data: Theme.of(context).copyWith(dividerColor: Colors.transparent), + child: ExpansionTile( + initiallyExpanded: hasConnectedDevice, + title: Text( + catList[categoryIndex].friendly, + style: Theme.of(context).textTheme.titleLarge, + ), + children: [ + GridView.builder( + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 125), + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: actionsForCat.length, + itemBuilder: (BuildContext context, int actionIndex) { + BaseAction baseAction = actionsForCat[actionIndex]; + bool isSelected = selected.contains(baseAction); + return TweenAnimationBuilder( + builder: (context, value, child) { + Color? color = Color.lerp(Theme.of(context).colorScheme.primary, Theme.of(context).cardColor, value); + return Card( + clipBehavior: Clip.antiAlias, + elevation: 2, + color: color, + child: cardChild(isSelected, baseAction, color!), + ); + }, + tween: isSelected ? Tween(begin: 1, end: 0) : Tween(begin: 0, end: 1), + duration: animationTransitionDuration, + ); + }, + ), + ], + ), + ); + }, + ), + ], + ), + ); } InkWell cardChild(bool isSelected, BaseAction baseAction, Color color) { diff --git a/lib/Frontend/pages/actions.dart b/lib/Frontend/pages/actions.dart index 162cd3170..0932cab5a 100644 --- a/lib/Frontend/pages/actions.dart +++ b/lib/Frontend/pages/actions.dart @@ -49,9 +49,11 @@ class _ActionPageBuilderState extends ConsumerState { return MultiValueListenableBuilder( valueListenables: knownDevices.isEmpty ? [ValueNotifier(ConnectivityState.disconnected)] : knownDevices.values.map((e) => e.deviceConnectionState).toList(), builder: (BuildContext context, List values, Widget? child) { - Map knownDevicesFiltered = Map.fromEntries(knownDevices.entries.where( - (element) => element.value.deviceConnectionState.value == ConnectivityState.connected, - )); + Map knownDevicesFiltered = Map.fromEntries( + knownDevices.entries.where( + (element) => element.value.deviceConnectionState.value == ConnectivityState.connected, + ), + ); return MultiValueListenableBuilder( builder: (context, values, child) { Map> actionsCatMap = ref.read(getAvailableActionsProvider); @@ -64,29 +66,30 @@ class _ActionPageBuilderState extends ConsumerState { shrinkWrap: true, children: [ AnimatedCrossFade( - firstChild: PageInfoCard( - text: actionsFavoriteTip(), - ), - secondChild: GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: largerCards ? 250 : 125), - itemCount: actionsCatMap.values.flattened + firstChild: PageInfoCard( + text: actionsFavoriteTip(), + ), + secondChild: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: largerCards ? 250 : 125), + itemCount: actionsCatMap.values.flattened + .where( + (element) => ref.watch(favoriteActionsProvider).any((favorite) => favorite.actionUUID == element.uuid), + ) + .length, + itemBuilder: (BuildContext context, int index) { + BaseAction baseAction = actionsCatMap.values.flattened .where( (element) => ref.watch(favoriteActionsProvider).any((favorite) => favorite.actionUUID == element.uuid), ) - .length, - itemBuilder: (BuildContext context, int index) { - BaseAction baseAction = actionsCatMap.values.flattened - .where( - (element) => ref.watch(favoriteActionsProvider).any((favorite) => favorite.actionUUID == element.uuid), - ) - .toList()[index]; - return getActionCard(index, knownDevicesFiltered, baseAction, largerCards); - }, - ), - crossFadeState: actionsCatMap.values.flattened.where((element) => ref.read(favoriteActionsProvider.notifier).contains(element)).isEmpty ? CrossFadeState.showFirst : CrossFadeState.showSecond, - duration: animationTransitionDuration), + .toList()[index]; + return getActionCard(index, knownDevicesFiltered, baseAction, largerCards); + }, + ), + crossFadeState: actionsCatMap.values.flattened.where((element) => ref.read(favoriteActionsProvider.notifier).contains(element)).isEmpty ? CrossFadeState.showFirst : CrossFadeState.showSecond, + duration: animationTransitionDuration, + ), ListView.builder( shrinkWrap: true, itemCount: catList.length, @@ -123,7 +126,7 @@ class _ActionPageBuilderState extends ConsumerState { }, ); }, - ) + ), ], ), ); @@ -152,7 +155,7 @@ class _ActionPageBuilderState extends ConsumerState { color: color, elevation: 1, child: InkWell( - onLongPress: () { + onLongPress: () async { if (HiveProxy.getOrDefault(settings, haptics, defaultValue: hapticsDefault)) { HapticFeedback.mediumImpact(); setState( @@ -225,7 +228,7 @@ class _ActionPageBuilderState extends ConsumerState { style: Theme.of(context).textTheme.labelLarge!.copyWith(color: textColor), textScaler: TextScaler.linear(largerCards ? 2 : 1), ), - ) + ), ], ), ), diff --git a/lib/Frontend/pages/custom_audio.dart b/lib/Frontend/pages/custom_audio.dart index 2bc55bc06..ff9064078 100644 --- a/lib/Frontend/pages/custom_audio.dart +++ b/lib/Frontend/pages/custom_audio.dart @@ -5,10 +5,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:tail_app/Backend/Definitions/Action/base_action.dart'; -import 'package:tail_app/Backend/audio.dart'; import 'package:uuid/uuid.dart'; +import '../../Backend/Definitions/Action/base_action.dart'; +import '../../Backend/audio.dart'; import '../Widgets/tutorial_card.dart'; import '../translation_string_definitions.dart'; @@ -26,106 +26,109 @@ class _CustomAudioState extends ConsumerState { Widget build(BuildContext context) { List userAudioActions = ref.watch(userAudioActionsProvider); return Scaffold( - appBar: AppBar( - title: Text(audioPage()), - ), - floatingActionButton: FloatingActionButton.extended( - onPressed: () async { - _audioLogger.info("Opening file dialog"); - FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.audio, withReadStream: true); - if (result != null) { - _audioLogger.info("Selected file"); - PlatformFile file = result.files.first; - final Directory appDir = await getApplicationSupportDirectory(); - Directory audioDir = Directory("${appDir.path}/audio"); - await audioDir.create(); - File storedAudioFilePath = File("${audioDir.path}/${file.name}"); - _audioLogger.info("File path ${storedAudioFilePath.path}"); - _audioLogger.info("Selected file Path ${file.path}"); - Stream> openRead = file.readStream!; - IOSink ioSinkWrite = storedAudioFilePath.openWrite(); - await ioSinkWrite.addStream(openRead); - ioSinkWrite.close(); - _audioLogger.info("Wrote file to app storage"); - AudioAction action = AudioAction( - name: file.name.substring(0, file.name.lastIndexOf(".")).replaceAll("_", " ").replaceAll("-", " "), - uuid: const Uuid().v4(), - file: storedAudioFilePath.path, - ); - setState(() { - ref.read(userAudioActionsProvider.notifier).add(action); - }); - } - //Open File Picker + appBar: AppBar( + title: Text(audioPage()), + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () async { + _audioLogger.info("Opening file dialog"); + FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.audio, withReadStream: true); + if (result != null) { + _audioLogger.info("Selected file"); + PlatformFile file = result.files.first; + final Directory appDir = await getApplicationSupportDirectory(); + Directory audioDir = Directory("${appDir.path}/audio"); + await audioDir.create(); + File storedAudioFilePath = File("${audioDir.path}/${file.name}"); + _audioLogger + ..info("File path ${storedAudioFilePath.path}") + ..info("Selected file Path ${file.path}"); + Stream> openRead = file.readStream!; + IOSink ioSinkWrite = storedAudioFilePath.openWrite(); + await ioSinkWrite.addStream(openRead); + ioSinkWrite.close(); + _audioLogger.info("Wrote file to app storage"); + AudioAction action = AudioAction( + name: file.name.substring(0, file.name.lastIndexOf(".")).replaceAll("_", " ").replaceAll("-", " "), + uuid: const Uuid().v4(), + file: storedAudioFilePath.path, + ); + setState(() { + ref.read(userAudioActionsProvider.notifier).add(action); + }); + } + //Open File Picker + }, + icon: const Icon(Icons.add), + label: Text(audioAdd()), + ), + body: ListView( + children: [ + PageInfoCard( + text: audioTipCard(), + ), + ListView.builder( + itemCount: userAudioActions.length, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + AudioAction audioAction = userAudioActions[index]; + return ListTile( + title: Text(audioAction.name), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: () async { + editModal(context, audioAction); + }, + tooltip: audioEdit(), + icon: const Icon(Icons.edit), + ), + IconButton( + onPressed: () async { + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: Text(audioDelete()), + content: Text(audioDeleteDescription()), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: Text(cancel()), + ), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: Text(ok()), + ), + ], + ), + ).then((value) async { + if (value ?? true) { + ref.read(userAudioActionsProvider.notifier).remove(audioAction); + File storedAudioFilePath = File(audioAction.file); + await storedAudioFilePath.delete(); + setState(() { + _audioLogger.info("Deleted audio file"); + }); + } + }); + }, //TODO: Show dialog, then delete record and file. + tooltip: audioDelete(), + icon: const Icon(Icons.delete), + ), + ], + ), + onTap: () async => playSound(audioAction.file), + ); }, - icon: const Icon(Icons.add), - label: Text(audioAdd())), - body: ListView( - children: [ - PageInfoCard( - text: audioTipCard(), - ), - ListView.builder( - itemCount: userAudioActions.length, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - AudioAction audioAction = userAudioActions[index]; - return ListTile( - title: Text(audioAction.name), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - onPressed: () { - editModal(context, audioAction); - }, - tooltip: audioEdit(), - icon: const Icon(Icons.edit), - ), - IconButton( - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) => AlertDialog( - title: Text(audioDelete()), - content: Text(audioDeleteDescription()), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: Text(cancel()), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: Text(ok()), - ), - ], - ), - ).then((value) async { - if (value == true) { - ref.read(userAudioActionsProvider.notifier).remove(audioAction); - File storedAudioFilePath = File(audioAction.file); - await storedAudioFilePath.delete(); - setState(() { - _audioLogger.info("Deleted audio file"); - }); - } - }); - }, //TODO: Show dialog, then delete record and file. - tooltip: audioDelete(), - icon: const Icon(Icons.delete), - ) - ], - ), - onTap: () => playSound(audioAction.file), - ); - }, - ), - ], - )); + ), + ], + ), + ); } - void editModal(BuildContext context, AudioAction audioAction) { + Future editModal(BuildContext context, AudioAction audioAction) async { showModalBottomSheet( context: context, showDragHandle: true, @@ -161,7 +164,7 @@ class _CustomAudioState extends ConsumerState { ref.watch(userAudioActionsProvider.notifier).store(); }, ), - ) + ), ], ); }, diff --git a/lib/Frontend/pages/developer/bluetooth_console.dart b/lib/Frontend/pages/developer/bluetooth_console.dart index 61405cc98..b37711fd5 100644 --- a/lib/Frontend/pages/developer/bluetooth_console.dart +++ b/lib/Frontend/pages/developer/bluetooth_console.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_message.dart'; -import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; +import '../../../Backend/Bluetooth/bluetooth_message.dart'; +import '../../../Backend/Definitions/Device/device_definition.dart'; class BluetoothConsole extends StatefulWidget { final BaseStatefulDevice device; - const BluetoothConsole({super.key, required this.device}); + const BluetoothConsole({required this.device, super.key}); @override State createState() => _BluetoothConsoleState(); @@ -61,7 +61,7 @@ class _BluetoothConsoleState extends State { }, icon: const Icon(Icons.send), ), - ) + ), ], ), ); @@ -70,8 +70,8 @@ class _BluetoothConsoleState extends State { class DisplayLog extends StatefulWidget { const DisplayLog({ - super.key, required this.widget, + super.key, }); final BluetoothConsole widget; diff --git a/lib/Frontend/pages/developer/developer_menu.dart b/lib/Frontend/pages/developer/developer_menu.dart index 664c46f21..2c8269780 100644 --- a/lib/Frontend/pages/developer/developer_menu.dart +++ b/lib/Frontend/pages/developer/developer_menu.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:tail_app/main.dart'; import '../../../Backend/Bluetooth/bluetooth_manager_plus.dart'; import '../../../Backend/logging_wrappers.dart'; import '../../../constants.dart'; +import '../../../main.dart'; class DeveloperMenu extends ConsumerStatefulWidget { const DeveloperMenu({super.key}); @@ -28,7 +28,7 @@ class _DeveloperMenuState extends ConsumerState { title: const Text("Logs"), leading: const Icon(Icons.list), subtitle: const Text("Application Logs"), - onTap: () { + onTap: () async { context.push("/settings/developer/logs"); }, ), @@ -44,7 +44,7 @@ class _DeveloperMenuState extends ConsumerState { title: const Text(hasCompletedOnboarding), trailing: Switch( value: HiveProxy.getOrDefault(settings, hasCompletedOnboarding, defaultValue: hasCompletedOnboardingDefault) == hasCompletedOnboardingVersionToAgree, - onChanged: (bool value) { + onChanged: (bool value) async { setState( () { HiveProxy.put(settings, hasCompletedOnboarding, value ? hasCompletedOnboardingVersionToAgree : hasCompletedOnboardingDefault); @@ -57,7 +57,7 @@ class _DeveloperMenuState extends ConsumerState { title: const Text(shouldDisplayReview), trailing: Switch( value: HiveProxy.getOrDefault(settings, shouldDisplayReview, defaultValue: shouldDisplayReviewDefault), - onChanged: (bool value) { + onChanged: (bool value) async { setState( () { HiveProxy.put(settings, shouldDisplayReview, value); @@ -70,7 +70,7 @@ class _DeveloperMenuState extends ConsumerState { title: const Text(hasDisplayedReview), trailing: Switch( value: HiveProxy.getOrDefault(settings, hasDisplayedReview, defaultValue: hasDisplayedReviewDefault), - onChanged: (bool value) { + onChanged: (bool value) async { setState( () { HiveProxy.put(settings, hasDisplayedReview, value); @@ -86,7 +86,7 @@ class _DeveloperMenuState extends ConsumerState { max: 6, min: 0, value: HiveProxy.getOrDefault(settings, gearDisconnectCount, defaultValue: gearDisconnectCountDefault).toDouble(), - onChanged: (double value) { + onChanged: (double value) async { setState(() { HiveProxy.put(settings, gearDisconnectCount, value.toInt()); }); @@ -97,7 +97,7 @@ class _DeveloperMenuState extends ConsumerState { title: const Text(showDebugging), trailing: Switch( value: HiveProxy.getOrDefault(settings, showDebugging, defaultValue: showDebuggingDefault), - onChanged: (bool value) { + onChanged: (bool value) async { setState( () { HiveProxy.put(settings, showDebugging, value); @@ -133,7 +133,7 @@ class _DeveloperMenuState extends ConsumerState { ListTile( title: const Text("LatestPostId"), subtitle: Text('${HiveProxy.getOrDefault(notificationBox, latestPost, defaultValue: defaultPostId)}'), - ) + ), ], ), ); diff --git a/lib/Frontend/pages/developer/developer_pincode.dart b/lib/Frontend/pages/developer/developer_pincode.dart index 1d0285ca3..7aa3ccecf 100644 --- a/lib/Frontend/pages/developer/developer_pincode.dart +++ b/lib/Frontend/pages/developer/developer_pincode.dart @@ -24,7 +24,7 @@ class _DeveloperPincodeState extends State { width: 80, ), onCancelled: () => context.pop(), - onUnlocked: () { + onUnlocked: () async { HiveProxy.put(settings, showDebugging, true); context.pop(); }, diff --git a/lib/Frontend/pages/direct_gear_control.dart b/lib/Frontend/pages/direct_gear_control.dart index f87d3391f..7d3f48b53 100644 --- a/lib/Frontend/pages/direct_gear_control.dart +++ b/lib/Frontend/pages/direct_gear_control.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_joystick/flutter_joystick.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:tail_app/Frontend/Widgets/speed_widget.dart'; import 'package:vector_math/vector_math.dart'; import '../../Backend/Bluetooth/bluetooth_manager.dart'; @@ -14,6 +13,7 @@ import '../../Backend/logging_wrappers.dart'; import '../../Backend/move_lists.dart'; import '../../constants.dart'; import '../Widgets/device_type_widget.dart'; +import '../Widgets/speed_widget.dart'; import '../Widgets/tutorial_card.dart'; import '../translation_string_definitions.dart'; @@ -55,69 +55,70 @@ class _JoystickState extends ConsumerState { PageInfoCard(text: joystickWarning()), const GearOutOfDateWarning(), Expanded( - child: Align( - alignment: Alignment.center, - child: Joystick( - mode: JoystickMode.all, - onStickDragEnd: () async { - await Future.delayed(Duration(milliseconds: (speed * 20).toInt())); - Move move = Move(); - move.moveType = MoveType.home; - if (!context.mounted) { - return; - } - ref.read(knownDevicesProvider).values.forEach( - (element) { - generateMoveCommand(move, element, CommandType.direct).forEach( - (message) { - message.responseMSG = null; - message.priority = Priority.high; - element.commandQueue.addCommand(message); - }, - ); - }, - ); - }, - base: const Card( - elevation: 1, - shape: CircleBorder(), - child: SizedBox.square(dimension: 300), - ), - stick: Card( - elevation: 2, - shape: const CircleBorder(), - color: Theme.of(context).colorScheme.primary, - child: const SizedBox.square(dimension: 100), - ), - listener: (details) { - setState( - () { - if (HiveProxy.getOrDefault(settings, haptics, defaultValue: hapticsDefault)) { - HapticFeedback.selectionClick(); - } - x = details.x; - y = details.y; + child: Align( + alignment: Alignment.center, + child: Joystick( + mode: JoystickMode.all, + onStickDragEnd: () async { + await Future.delayed(Duration(milliseconds: (speed * 20).toInt())); + Move move = Move()..moveType = MoveType.home; + if (!context.mounted) { + return; + } + ref.read(knownDevicesProvider).values.forEach( + (element) { + generateMoveCommand(move, element, CommandType.direct).forEach( + (message) { + message + ..responseMSG = null + ..priority = Priority.high; + element.commandQueue.addCommand(message); + }, + ); + }, + ); + }, + base: const Card( + elevation: 1, + shape: CircleBorder(), + child: SizedBox.square(dimension: 300), + ), + stick: Card( + elevation: 2, + shape: const CircleBorder(), + color: Theme.of(context).colorScheme.primary, + child: const SizedBox.square(dimension: 100), + ), + listener: (details) async { + setState( + () { + if (HiveProxy.getOrDefault(settings, haptics, defaultValue: hapticsDefault)) { + HapticFeedback.selectionClick(); + } + x = details.x; + y = details.y; - double sign = x.sign; - direction = degrees(atan2(y.abs(), x.abs())); // 0-90 - magnitude = sqrt(pow(x.abs(), 2).toDouble() + pow(y.abs(), 2).toDouble()); + double sign = x.sign; + direction = degrees(atan2(y.abs(), x.abs())); // 0-90 + magnitude = sqrt(pow(x.abs(), 2).toDouble() + pow(y.abs(), 2).toDouble()); - double secondServo = ((((direction - 0) * (128 - 0)) / (90 - 0)) + 0).clamp(0, 128); - double primaryServo = ((((magnitude - 0) * (128 - 0)) / (1 - 0)) + 0).clamp(0, 128); - if (sign > 0) { - left = primaryServo; - right = secondServo; - } else { - right = primaryServo; - left = secondServo; - } - }, - ); - sendMove(); - }, - period: Duration(milliseconds: (speed * 20).toInt()), + double secondServo = ((((direction - 0) * (128 - 0)) / (90 - 0)) + 0).clamp(0, 128); + double primaryServo = ((((magnitude - 0) * (128 - 0)) / (1 - 0)) + 0).clamp(0, 128); + if (sign > 0) { + left = primaryServo; + right = secondServo; + } else { + right = primaryServo; + left = secondServo; + } + }, + ); + sendMove(); + }, + period: Duration(milliseconds: (speed * 20).toInt()), + ), ), - )), + ), ], ), if (HiveProxy.getOrDefault(settings, showDebugging, defaultValue: showDebuggingDefault)) ...[ @@ -137,7 +138,7 @@ class _JoystickState extends ConsumerState { ], ), ), - ) + ), ], ExpansionTile( title: Text(settingsPage()), @@ -182,7 +183,7 @@ class _JoystickState extends ConsumerState { }, ), ], - ) + ), ], ), ), @@ -192,17 +193,18 @@ class _JoystickState extends ConsumerState { } void sendMove() { - Move move = Move(); - move.easingType = easingType; - move.speed = speed; - move.rightServo = right; - move.leftServo = left; + Move move = Move() + ..easingType = easingType + ..speed = speed + ..rightServo = right + ..leftServo = left; ref.read(knownDevicesProvider).values.where((element) => deviceTypes.contains(element.baseDeviceDefinition.deviceType)).forEach( (element) { generateMoveCommand(move, element, CommandType.direct).forEach( (message) { - message.responseMSG = null; - message.priority = Priority.high; + message + ..responseMSG = null + ..priority = Priority.high; element.commandQueue.addCommand(message); }, ); diff --git a/lib/Frontend/pages/home.dart b/lib/Frontend/pages/home.dart index dd25a2a9c..d7307d5bd 100644 --- a/lib/Frontend/pages/home.dart +++ b/lib/Frontend/pages/home.dart @@ -4,10 +4,10 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart' as log; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager_plus.dart'; -import 'package:tail_app/Frontend/Widgets/base_card.dart'; -import 'package:tail_app/constants.dart'; +import '../../Backend/Bluetooth/bluetooth_manager.dart'; +import '../../Backend/Bluetooth/bluetooth_manager_plus.dart'; +import '../Widgets/base_card.dart'; +import '../../constants.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../Backend/logging_wrappers.dart'; @@ -68,9 +68,7 @@ class _HomeState extends ConsumerState { child: Column( mainAxisSize: MainAxisSize.min, children: [ - if (ref - .read(knownDevicesProvider) - .isNotEmpty && !HiveProxy.getOrDefault(settings, alwaysScanning, defaultValue: alwaysScanningDefault) && !HiveProxy.getOrDefault(settings, hideTutorialCards, defaultValue: hideTutorialCardsDefault)) ...[ + if (ref.read(knownDevicesProvider).isNotEmpty && !HiveProxy.getOrDefault(settings, alwaysScanning, defaultValue: alwaysScanningDefault) && !HiveProxy.getOrDefault(settings, hideTutorialCards, defaultValue: hideTutorialCardsDefault)) ...[ ListTile( leading: const Icon(Icons.info), subtitle: Text(homeContinuousScanningOffDescription()), @@ -140,10 +138,7 @@ class _HomeState extends ConsumerState { ListTile( title: Text( homeNewsTitle(), - style: Theme - .of(context) - .textTheme - .titleLarge, + style: Theme.of(context).textTheme.titleLarge, ), ), child!, diff --git a/lib/Frontend/pages/intro.dart b/lib/Frontend/pages/intro.dart index 7766f4478..0f3ad3d75 100644 --- a/lib/Frontend/pages/intro.dart +++ b/lib/Frontend/pages/intro.dart @@ -1,18 +1,20 @@ +import 'dart:async'; + import 'package:app_settings/app_settings.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:introduction_screen/introduction_screen.dart'; import 'package:logging/logging.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager_plus.dart'; -import 'package:tail_app/Frontend/Widgets/lottie_lazy_load.dart'; -import 'package:tail_app/Frontend/translation_string_definitions.dart'; import 'package:url_launcher/url_launcher.dart'; +import '../../Backend/Bluetooth/bluetooth_manager_plus.dart'; import '../../Backend/logging_wrappers.dart'; import '../../constants.dart'; import '../../gen/assets.gen.dart'; import '../../main.dart'; +import '../Widgets/lottie_lazy_load.dart'; +import '../translation_string_definitions.dart'; import '../utils.dart'; class OnBoardingPage extends ConsumerStatefulWidget { @@ -45,7 +47,7 @@ class OnBoardingPageState extends ConsumerState { @override Widget build(BuildContext context) { - setupSystemColor(context); + unawaited(setupSystemColor(context)); var pageDecoration = PageDecoration( titleTextStyle: const TextStyle(fontSize: 28.0, fontWeight: FontWeight.w700), bodyTextStyle: const TextStyle(fontSize: 19.0), @@ -81,7 +83,7 @@ class OnBoardingPageState extends ConsumerState { padding: const EdgeInsets.only(top: 16, right: 16), child: InkWell( child: _buildImage(Assets.tCLogoTransparentNoText.path, 60), - onLongPress: () { + onLongPress: () async { _introLogger.info("Open Logs"); context.push("/settings/developer/logs"); }, @@ -125,19 +127,20 @@ class OnBoardingPageState extends ConsumerState { FilledButton( onPressed: privacyAccepted ? null - : () { + : () async { setState(() { _introLogger.info("Accepted Privacy Policy"); privacyAccepted = true; - HiveProxy.put(settings, allowErrorReporting, true); - HiveProxy.put(settings, allowAnalytics, true); + HiveProxy + ..put(settings, allowErrorReporting, true) + ..put(settings, allowAnalytics, true); introKey.currentState?.next(); }); }, child: Text( onboardingPrivacyPolicyAcceptButtonLabel(), ), - ) + ), ], ), decoration: pageDecoration, @@ -155,7 +158,7 @@ class OnBoardingPageState extends ConsumerState { FilledButton( onPressed: bluetoothEnabled ? null - : () { + : () async { AppSettings.openAppSettings(type: AppSettingsType.bluetooth); }, child: Text( @@ -182,7 +185,7 @@ class OnBoardingPageState extends ConsumerState { child: Text( onboardingBluetoothRequestButtonLabel(), ), - ) + ), ], ), decoration: pageDecoration, diff --git a/lib/Frontend/pages/markdown_viewer.dart b/lib/Frontend/pages/markdown_viewer.dart index b4a44b2cc..c7b355533 100644 --- a/lib/Frontend/pages/markdown_viewer.dart +++ b/lib/Frontend/pages/markdown_viewer.dart @@ -11,7 +11,7 @@ class MarkdownInfo { } class MarkdownViewer extends StatelessWidget { - const MarkdownViewer({super.key, required this.markdownInfo}); + const MarkdownViewer({required this.markdownInfo, super.key}); final MarkdownInfo markdownInfo; @@ -27,7 +27,7 @@ class MarkdownViewer extends StatelessWidget { md.ExtensionSet.gitHubFlavored.blockSyntaxes, [md.EmojiSyntax(), ...md.ExtensionSet.gitHubFlavored.inlineSyntaxes], ), - onTapLink: (linkText, linkUrl, linkTitle) async => await launchUrl(Uri.parse(linkUrl!)), + onTapLink: (linkText, linkUrl, linkTitle) async => launchUrl(Uri.parse(linkUrl!)), ), ); } diff --git a/lib/Frontend/pages/more.dart b/lib/Frontend/pages/more.dart index d0815c0b5..727f87c53 100644 --- a/lib/Frontend/pages/more.dart +++ b/lib/Frontend/pages/more.dart @@ -6,14 +6,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:tail_app/Frontend/pages/markdown_viewer.dart'; -import 'package:tail_app/Frontend/translation_string_definitions.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../Backend/logging_wrappers.dart'; import '../../constants.dart'; import '../../gen/assets.gen.dart'; +import '../translation_string_definitions.dart'; import '../utils.dart'; +import 'markdown_viewer.dart'; class More extends ConsumerStatefulWidget { const More({super.key}); @@ -39,7 +39,7 @@ class _MoreState extends ConsumerState { title: Text(joyStickPage()), subtitle: Text(joyStickPageDescription()), leading: const Icon(Icons.gamepad), - onTap: () { + onTap: () async { context.push('/joystick'); }, ), @@ -47,7 +47,7 @@ class _MoreState extends ConsumerState { title: Text(sequencesPage()), subtitle: Text(sequencesPageDescription()), leading: const Icon(Icons.list), - onTap: () { + onTap: () async { context.push('/moveLists'); }, ), @@ -55,7 +55,7 @@ class _MoreState extends ConsumerState { title: Text(audioPage()), subtitle: Text(audioEditDescription()), leading: const Icon(Icons.audio_file), - onTap: () { + onTap: () async { context.push('/customAudio'); }, ), @@ -63,7 +63,7 @@ class _MoreState extends ConsumerState { title: Text(settingsPage()), subtitle: Text(settingsDescription()), leading: const Icon(Icons.settings), - onTap: () { + onTap: () async { context.push('/settings'); }, ), @@ -77,10 +77,7 @@ class _MoreState extends ConsumerState { ListTile( title: Text( moreManualTitle(), - style: Theme - .of(context) - .textTheme - .headlineLarge, + style: Theme.of(context).textTheme.headlineLarge, ), ), PdfWidget(name: moreManualMiTailTitle(), url: "https://thetailcompany.com/mitail.pdf${getOutboundUtm()}"), @@ -96,10 +93,7 @@ class _MoreState extends ConsumerState { ListTile( title: Text( moreUsefulLinksTitle(), - style: Theme - .of(context) - .textTheme - .headlineLarge, + style: Theme.of(context).textTheme.headlineLarge, ), ), ListTile( @@ -141,7 +135,7 @@ class _MoreState extends ConsumerState { onTap: () async { await launchUrl(Uri.parse('https://github.com/Codel1417/tail_app')); }, - onLongPress: () { + onLongPress: () async { if (HiveProxy.getOrDefault(settings, showDebugging, defaultValue: showDebuggingDefault)) { return; } @@ -158,20 +152,19 @@ class _MoreState extends ConsumerState { ListTile( title: Text(aboutPage()), leading: const Icon(Icons.info), - onTap: () { + onTap: () async { PackageInfo.fromPlatform().then( - (value) => - showLicensePage( - context: context, - useRootNavigator: true, - applicationVersion: "${value.version} (${value.buildNumber})", - applicationLegalese: "Developed by Code-Floof for the community. Open Source GPL 3.0 Licensed", - applicationIcon: Image.asset( - Assets.tCLogoTransparentNoText.path, - width: 150, - height: 150, - ), - ), + (value) => showLicensePage( + context: context, + useRootNavigator: true, + applicationVersion: "${value.version} (${value.buildNumber})", + applicationLegalese: "Developed by Code-Floof for the community. Open Source GPL 3.0 Licensed", + applicationIcon: Image.asset( + Assets.tCLogoTransparentNoText.path, + width: 150, + height: 150, + ), + ), ); }, ), @@ -184,7 +177,7 @@ class PdfWidget extends StatefulWidget { final String name; final String url; - const PdfWidget({super.key, required this.name, required this.url}); + const PdfWidget({required this.name, required this.url, super.key}); @override State createState() => _PdfWidgetState(); @@ -230,7 +223,7 @@ class _PdfWidgetState extends State { ), onReceiveProgress: (current, total) { setState( - () { + () { progress = current / total; }, ); @@ -243,16 +236,17 @@ class _PdfWidgetState extends State { } } else { setState( - () { + () { progress = 0; }, ); } } catch (e) { - transaction.throwable = e; - transaction.status = const SpanStatus.internalError(); + transaction + ..throwable = e + ..status = const SpanStatus.internalError(); setState( - () { + () { progress = 0; }, ); diff --git a/lib/Frontend/pages/move_list.dart b/lib/Frontend/pages/move_list.dart index 05f14c149..c02ff2be1 100644 --- a/lib/Frontend/pages/move_list.dart +++ b/lib/Frontend/pages/move_list.dart @@ -4,17 +4,17 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart'; -import 'package:tail_app/Backend/Definitions/Action/base_action.dart'; -import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; -import 'package:tail_app/Backend/move_lists.dart'; -import 'package:tail_app/Frontend/Widgets/speed_widget.dart'; import 'package:uuid/uuid.dart'; +import '../../Backend/Bluetooth/bluetooth_manager.dart'; +import '../../Backend/Definitions/Action/base_action.dart'; +import '../../Backend/Definitions/Device/device_definition.dart'; import '../../Backend/logging_wrappers.dart'; +import '../../Backend/move_lists.dart'; import '../../constants.dart'; import '../../main.dart'; import '../Widgets/device_type_widget.dart'; +import '../Widgets/speed_widget.dart'; import '../Widgets/tutorial_card.dart'; import '../translation_string_definitions.dart'; @@ -30,16 +30,16 @@ class _MoveListViewState extends ConsumerState { Widget build(BuildContext context) { final List allMoveLists = ref.watch(moveListsProvider); return Scaffold( - appBar: AppBar(title: Text(sequencesPage())), - floatingActionButton: FloatingActionButton.extended( - icon: const Icon(Icons.add), - onPressed: () { - setState(() { - ref.watch(moveListsProvider.notifier).add(MoveList(name: sequencesPage(), deviceCategory: DeviceType.values.toList(), actionCategory: ActionCategory.sequence, uuid: const Uuid().v4())); - ref.watch(moveListsProvider.notifier).store(); - }); - plausible.event(name: "Add Sequence"); - context.push("/moveLists/editMoveList", extra: ref.watch(moveListsProvider).last).then((value) => setState(() { + appBar: AppBar(title: Text(sequencesPage())), + floatingActionButton: FloatingActionButton.extended( + icon: const Icon(Icons.add), + onPressed: () async { + setState(() { + ref.watch(moveListsProvider.notifier).add(MoveList(name: sequencesPage(), deviceCategory: DeviceType.values.toList(), actionCategory: ActionCategory.sequence, uuid: const Uuid().v4())); + }); + plausible.event(name: "Add Sequence"); + context.push("/moveLists/editMoveList", extra: ref.watch(moveListsProvider).last).then( + (value) => setState(() { if (value != null) { if (ref.watch(moveListsProvider).isNotEmpty) { ref.watch(moveListsProvider).last = value; @@ -48,61 +48,63 @@ class _MoveListViewState extends ConsumerState { } ref.watch(moveListsProvider.notifier).store(); } - })); - }, - label: Text(sequencesPage()), - ), - body: SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics(), - child: Column( - children: [ - PageInfoCard( - text: sequencesInfoDescription(), - ), - const GearOutOfDateWarning(), - ListView.builder( - itemCount: allMoveLists.length, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - return ListTile( - key: Key('$index'), - title: Text(allMoveLists[index].name), - subtitle: Text("${allMoveLists[index].moves.length} move(s)"), - //TODO: Localize - trailing: IconButton( - tooltip: sequencesEdit(), - icon: const Icon(Icons.edit), - onPressed: () { - context.push("/moveLists/editMoveList", extra: allMoveLists[index]).then( - (value) => setState( - () { - if (value != null) { - allMoveLists[index] = value; - ref.watch(moveListsProvider.notifier).store(); - } - }, - ), - ); - }, - ), - onTap: () async { - if (HiveProxy.getOrDefault(settings, haptics, defaultValue: hapticsDefault)) { - HapticFeedback.selectionClick(); - } - for (BaseStatefulDevice element in ref.watch(knownDevicesProvider).values.where((element) => allMoveLists[index].deviceCategory.contains(element.baseDeviceDefinition.deviceType))) { - if (HiveProxy.getOrDefault(settings, kitsuneModeToggle, defaultValue: kitsuneModeDefault)) { - await Future.delayed(Duration(milliseconds: Random().nextInt(kitsuneDelayRange))); - } - runAction(allMoveLists[index], element); - } + }), + ); + }, + label: Text(sequencesPage()), + ), + body: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: Column( + children: [ + PageInfoCard( + text: sequencesInfoDescription(), + ), + const GearOutOfDateWarning(), + ListView.builder( + itemCount: allMoveLists.length, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return ListTile( + key: Key('$index'), + title: Text(allMoveLists[index].name), + subtitle: Text("${allMoveLists[index].moves.length} move(s)"), + //TODO: Localize + trailing: IconButton( + tooltip: sequencesEdit(), + icon: const Icon(Icons.edit), + onPressed: () async { + context.push("/moveLists/editMoveList", extra: allMoveLists[index]).then( + (value) => setState( + () { + if (value != null) { + allMoveLists[index] = value; + ref.watch(moveListsProvider.notifier).store(); + } + }, + ), + ); }, - ); - }, - ), - ], - ), - )); + ), + onTap: () async { + if (HiveProxy.getOrDefault(settings, haptics, defaultValue: hapticsDefault)) { + HapticFeedback.selectionClick(); + } + for (BaseStatefulDevice element in ref.watch(knownDevicesProvider).values.where((element) => allMoveLists[index].deviceCategory.contains(element.baseDeviceDefinition.deviceType))) { + if (HiveProxy.getOrDefault(settings, kitsuneModeToggle, defaultValue: kitsuneModeDefault)) { + await Future.delayed(Duration(milliseconds: Random().nextInt(kitsuneDelayRange))); + } + runAction(allMoveLists[index], element); + } + }, + ); + }, + ), + ], + ), + ), + ); } } @@ -138,7 +140,7 @@ class _EditMoveList extends ConsumerState with TickerProviderState IconButton( icon: const Icon(Icons.delete), tooltip: sequencesEditDeleteTitle(), - onPressed: () { + onPressed: () async { showDialog( context: context, builder: (BuildContext context) => AlertDialog( @@ -155,15 +157,17 @@ class _EditMoveList extends ConsumerState with TickerProviderState ), ], ), - ).then((value) { - if (value == true) { - ref.watch(moveListsProvider.notifier).remove(moveList!); - ref.watch(moveListsProvider.notifier).store(); - context.pop(); + ).then((value) async { + if (value ?? true) { + await ref.watch(moveListsProvider.notifier).remove(moveList!); + await ref.watch(moveListsProvider.notifier).store(); + if (context.mounted) { + context.pop(); + } } }); }, - ) + ), ], ), floatingActionButton: moveList!.moves.length < 6 @@ -253,9 +257,9 @@ class _EditMoveList extends ConsumerState with TickerProviderState editModal(context, index); //context.push("/moveLists/editMoveList/editMove", extra: moveList!.moves[index]).then((value) => setState(() => moveList!.moves[index] = value!)); }, - ) + ), ], - onReorder: (int oldIndex, int newIndex) { + onReorder: (int oldIndex, int newIndex) async { if (oldIndex < newIndex) { newIndex -= 1; } @@ -267,7 +271,7 @@ class _EditMoveList extends ConsumerState with TickerProviderState ); ref.watch(moveListsProvider.notifier).store(); }, - ) + ), ], ), ), @@ -367,7 +371,7 @@ class _EditMoveList extends ConsumerState with TickerProviderState }, ).toList(), ), - ) + ), ], ), ListView( @@ -384,12 +388,12 @@ class _EditMoveList extends ConsumerState with TickerProviderState setEditState(() => move.time = value.roundToDouble()); }, ), - ) + ), ], ), ], ), - ) + ), ], ); }, @@ -398,7 +402,7 @@ class _EditMoveList extends ConsumerState with TickerProviderState ); }, ).whenComplete( - () { + () async { setState( () { moveList!.moves[index] = move; diff --git a/lib/Frontend/pages/ota_update.dart b/lib/Frontend/pages/ota_update.dart index 461041119..ad46ed1cf 100644 --- a/lib/Frontend/pages/ota_update.dart +++ b/lib/Frontend/pages/ota_update.dart @@ -11,11 +11,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager_plus.dart'; -import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; +import '../../Backend/Bluetooth/bluetooth_manager.dart'; +import '../../Backend/Bluetooth/bluetooth_manager_plus.dart'; +import '../../Backend/Definitions/Device/device_definition.dart'; import '../../Backend/firmware_update.dart'; import '../../Backend/logging_wrappers.dart'; import '../../constants.dart'; @@ -69,7 +69,7 @@ class _OtaUpdateState extends ConsumerState { baseStatefulDevice!.fwVersion.addListener(verListener); baseStatefulDevice!.fwInfo.addListener(fwInfoListener); if (firmwareInfo == null) { - baseStatefulDevice!.getFirmwareInfo(); + unawaited(baseStatefulDevice!.getFirmwareInfo()); } } @@ -77,7 +77,7 @@ class _OtaUpdateState extends ConsumerState { void dispose() { super.dispose(); if (!wakelockEnabledBeforehand) { - WakelockPlus.disable(); + unawaited(WakelockPlus.disable()); } if ([OtaState.download, OtaState.upload].contains(otaState)) { otaState == OtaState.error; @@ -86,7 +86,7 @@ class _OtaUpdateState extends ConsumerState { baseStatefulDevice!.fwVersion.removeListener(verListener); baseStatefulDevice!.fwInfo.removeListener(fwInfoListener); if (!HiveProxy.getOrDefault(settings, alwaysScanning, defaultValue: alwaysScanningDefault)) { - stopScan(); + unawaited(stopScan()); } timer?.cancel(); } @@ -145,7 +145,7 @@ class _OtaUpdateState extends ConsumerState { alignment: MainAxisAlignment.center, children: [ FilledButton( - onPressed: (firmwareInfo != null || firmwareFile != null) ? () => beginUpdate() : null, + onPressed: (firmwareInfo != null || firmwareFile != null) ? beginUpdate : null, child: Row( children: [ Icon( @@ -189,12 +189,12 @@ class _OtaUpdateState extends ConsumerState { } }, child: const Text("Select file"), - ) + ), ], ], ), ), - ) + ), ], if (otaState == OtaState.completed) ...[ Expanded( @@ -291,10 +291,12 @@ class _OtaUpdateState extends ConsumerState { Expanded( child: Center( child: ListTile( - subtitle: Builder(builder: (context) { - double progress = downloadProgress < 1 ? downloadProgress : uploadProgress; - return LinearProgressIndicator(value: otaState == OtaState.rebooting ? null : progress); - }), + subtitle: Builder( + builder: (context) { + double progress = downloadProgress < 1 ? downloadProgress : uploadProgress; + return LinearProgressIndicator(value: otaState == OtaState.rebooting ? null : progress); + }, + ), ), ), ), @@ -313,7 +315,7 @@ class _OtaUpdateState extends ConsumerState { ], ), ), - ) + ), ], ], ], @@ -347,14 +349,17 @@ class _OtaUpdateState extends ConsumerState { otaState = OtaState.download; downloadProgress = 0; }); - final transaction = Sentry.startTransaction('OTA Download', 'http'); - transaction.setTag("GearType", baseStatefulDevice!.baseDeviceDefinition.btName); + final transaction = Sentry.startTransaction('OTA Download', 'http')..setTag("GearType", baseStatefulDevice!.baseDeviceDefinition.btName); try { - final Response> rs = await (await initDio()).get>(firmwareInfo!.url, options: Options(responseType: ResponseType.bytes), onReceiveProgress: (current, total) { - setState(() { - downloadProgress = current / total; - }); - }); + final Response> rs = await (await initDio()).get>( + firmwareInfo!.url, + options: Options(responseType: ResponseType.bytes), + onReceiveProgress: (current, total) { + setState(() { + downloadProgress = current / total; + }); + }, + ); if (rs.statusCode == 200) { downloadProgress = 1; Digest digest = md5.convert(rs.data!); @@ -367,8 +372,9 @@ class _OtaUpdateState extends ConsumerState { } } } catch (e) { - transaction.throwable = e; - transaction.status = const SpanStatus.internalError(); + transaction + ..throwable = e + ..status = const SpanStatus.internalError(); otaState = OtaState.error; } transaction.finish(); @@ -444,8 +450,9 @@ class _OtaUpdateState extends ConsumerState { } catch (e, s) { _otaLogger.severe("Exception during ota upload:$e", e, s); if ((current + chunk.length) / total < 0.99) { - transaction.status = const SpanStatus.unknownError(); - transaction.throwable = e; + transaction + ..status = const SpanStatus.unknownError() + ..throwable = e; setState(() { otaState = OtaState.error; }); diff --git a/lib/Frontend/pages/settings.dart b/lib/Frontend/pages/settings.dart index 58d4a5931..46466a115 100644 --- a/lib/Frontend/pages/settings.dart +++ b/lib/Frontend/pages/settings.dart @@ -2,9 +2,9 @@ import 'package:flex_color_picker/flex_color_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; +import '../../Backend/Bluetooth/bluetooth_manager.dart'; import '../../Backend/Definitions/Device/device_definition.dart'; import '../../Backend/logging_wrappers.dart'; import '../../constants.dart'; @@ -48,58 +48,56 @@ class _SettingsState extends ConsumerState { borderRadius: 22, color: Color(HiveProxy.getOrDefault(settings, appColor, defaultValue: appColorDefault)), ), - onTap: () { + onTap: () async { plausible.event(page: "Settings/App Color"); showDialog( - context: context, - useRootNavigator: false, - useSafeArea: true, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - settingsAppColor(), - style: Theme - .of(context) - .textTheme - .titleLarge, + context: context, + useRootNavigator: false, + useSafeArea: true, + builder: (BuildContext context) { + return AlertDialog( + title: Text( + settingsAppColor(), + style: Theme.of(context).textTheme.titleLarge, + ), + actions: [ + TextButton( + onPressed: () { + HiveProxy.put(settings, appColor, appColorValue.value); + Navigator.of(context).pop(); + }, + child: Text( + ok(), + ), ), - actions: [ - TextButton( - onPressed: () { - HiveProxy.put(settings, appColor, appColorValue.value); - Navigator.of(context).pop(); - }, - child: Text( - ok(), - ), + TextButton( + onPressed: () { + appColorValue = Color(HiveProxy.getOrDefault(settings, appColor, defaultValue: appColorDefault)); + Navigator.of(context).pop(); + }, + child: Text( + cancel(), ), - TextButton( - onPressed: () { - appColorValue = Color(HiveProxy.getOrDefault(settings, appColor, defaultValue: appColorDefault)); - Navigator.of(context).pop(); + ), + ], + content: Wrap( + children: [ + ColorPicker( + color: appColorValue, + padding: EdgeInsets.zero, + onColorChanged: (Color color) => setState(() => appColorValue = color), + pickersEnabled: const { + ColorPickerType.both: false, + ColorPickerType.primary: true, + ColorPickerType.accent: true, + ColorPickerType.wheel: true, }, - child: Text( - cancel(), - ), - ) + ), ], - content: Wrap( - children: [ - ColorPicker( - color: appColorValue, - padding: EdgeInsets.zero, - onColorChanged: (Color color) => setState(() => appColorValue = color), - pickersEnabled: const { - ColorPickerType.both: false, - ColorPickerType.primary: true, - ColorPickerType.accent: true, - ColorPickerType.wheel: true, - }, - ) - ], - ), - ); - }).whenComplete(() => setState(() {})); + ), + ); + }, + ).whenComplete(() => setState(() {})); }, ), ListTile( @@ -108,9 +106,9 @@ class _SettingsState extends ConsumerState { subtitle: Text(settingsBatteryPercentageToggleSubTitle()), trailing: Switch( value: HiveProxy.getOrDefault(settings, showAccurateBattery, defaultValue: showAccurateBatteryDefault), - onChanged: (bool value) { + onChanged: (bool value) async { setState( - () { + () { HiveProxy.put(settings, showAccurateBattery, value); }, ); @@ -123,9 +121,9 @@ class _SettingsState extends ConsumerState { subtitle: Text(settingsLargerCardsToggleSubTitle()), trailing: Switch( value: HiveProxy.getOrDefault(settings, largerActionCardSize, defaultValue: largerActionCardSizeDefault), - onChanged: (bool value) { + onChanged: (bool value) async { setState( - () { + () { HiveProxy.put(settings, largerActionCardSize, value); }, ); @@ -138,9 +136,9 @@ class _SettingsState extends ConsumerState { subtitle: Text(settingsTutorialCardToggleSubTitle()), trailing: Switch( value: HiveProxy.getOrDefault(settings, hideTutorialCards, defaultValue: hideTutorialCardsDefault), - onChanged: (bool value) { + onChanged: (bool value) async { setState( - () { + () { HiveProxy.put(settings, hideTutorialCards, value); }, ); @@ -156,7 +154,7 @@ class _SettingsState extends ConsumerState { subtitle: Text(settingsAlwaysScanningToggleSubTitle()), trailing: Switch( value: HiveProxy.getOrDefault(settings, alwaysScanning, defaultValue: alwaysScanningDefault), - onChanged: (bool value) { + onChanged: (bool value) async { setState(() { HiveProxy.put(settings, alwaysScanning, value); }); @@ -169,7 +167,7 @@ class _SettingsState extends ConsumerState { subtitle: Text(settingsHapticsToggleSubTitle()), trailing: Switch( value: HiveProxy.getOrDefault(settings, haptics, defaultValue: hapticsDefault), - onChanged: (bool value) { + onChanged: (bool value) async { setState(() { HiveProxy.put(settings, haptics, value); }); @@ -182,14 +180,10 @@ class _SettingsState extends ConsumerState { subtitle: Text(settingsKeepScreenOnToggleSubTitle()), trailing: Switch( value: HiveProxy.getOrDefault(settings, keepAwake, defaultValue: keepAwakeDefault), - onChanged: (bool value) { + onChanged: (bool value) async { setState(() { HiveProxy.put(settings, keepAwake, value); - if (ref - .read(knownDevicesProvider) - .values - .where((element) => element.deviceConnectionState.value == ConnectivityState.connected) - .isNotEmpty) { + if (ref.read(knownDevicesProvider).values.where((element) => element.deviceConnectionState.value == ConnectivityState.connected).isNotEmpty) { if (value) { WakelockPlus.enable(); } else { @@ -206,9 +200,9 @@ class _SettingsState extends ConsumerState { subtitle: Text(settingsKitsuneToggleSubTitle()), trailing: Switch( value: HiveProxy.getOrDefault(settings, kitsuneModeToggle, defaultValue: kitsuneModeDefault), - onChanged: (bool value) { + onChanged: (bool value) async { setState( - () { + () { HiveProxy.put(settings, kitsuneModeToggle, value); }, ); @@ -221,9 +215,9 @@ class _SettingsState extends ConsumerState { subtitle: Text(scanDemoGearTip()), trailing: Switch( value: HiveProxy.getOrDefault(settings, showDemoGear, defaultValue: showDemoGearDefault), - onChanged: (bool value) { + onChanged: (bool value) async { setState( - () { + () { HiveProxy.put(settings, showDemoGear, value); }, ); @@ -239,7 +233,7 @@ class _SettingsState extends ConsumerState { subtitle: Text(settingsNewsletterToggleSubTitle()), trailing: Switch( value: HiveProxy.getOrDefault(settings, allowNewsletterNotifications, defaultValue: allowNewsletterNotificationsDefault), - onChanged: (bool value) { + onChanged: (bool value) async { setState(() { HiveProxy.put(settings, allowNewsletterNotifications, value); }); @@ -255,7 +249,7 @@ class _SettingsState extends ConsumerState { subtitle: Text(settingsAnalyticsToggleSubTitle()), trailing: Switch( value: HiveProxy.getOrDefault(settings, allowAnalytics, defaultValue: allowAnalyticsDefault), - onChanged: (bool value) { + onChanged: (bool value) async { setState(() { HiveProxy.put(settings, allowAnalytics, value); }); @@ -269,7 +263,7 @@ class _SettingsState extends ConsumerState { subtitle: Text(settingsErrorReportingToggleSubTitle()), trailing: Switch( value: HiveProxy.getOrDefault(settings, allowErrorReporting, defaultValue: allowErrorReportingDefault), - onChanged: (bool value) { + onChanged: (bool value) async { setState(() { HiveProxy.put(settings, allowErrorReporting, value); }); @@ -281,10 +275,10 @@ class _SettingsState extends ConsumerState { title: const Text("Development Menu"), leading: const Icon(Icons.bug_report), subtitle: const Text("It is illegal to read this message"), - onTap: () { + onTap: () async { context.push('/settings/developer'); }, - ) + ), ], ], ), diff --git a/lib/Frontend/pages/shell.dart b/lib/Frontend/pages/shell.dart index 15fbff669..f71367cbf 100644 --- a/lib/Frontend/pages/shell.dart +++ b/lib/Frontend/pages/shell.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:fading_edge_scrollview/fading_edge_scrollview.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flex_color_picker/flex_color_picker.dart'; @@ -8,21 +10,21 @@ import 'package:go_router/go_router.dart'; import 'package:hive_flutter/adapters.dart'; import 'package:in_app_review/in_app_review.dart'; import 'package:sentry_hive/sentry_hive.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager_plus.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_message.dart'; -import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; -import 'package:tail_app/Frontend/Widgets/back_button_to_close.dart'; -import 'package:tail_app/Frontend/Widgets/known_gear_scan_controller.dart'; -import 'package:tail_app/Frontend/Widgets/logging_shake.dart'; -import 'package:tail_app/Frontend/Widgets/snack_bar_overlay.dart'; import 'package:upgrader/upgrader.dart'; +import '../../Backend/Bluetooth/bluetooth_manager.dart'; +import '../../Backend/Bluetooth/bluetooth_manager_plus.dart'; +import '../../Backend/Bluetooth/bluetooth_message.dart'; +import '../../Backend/Definitions/Device/device_definition.dart'; import '../../Backend/logging_wrappers.dart'; import '../../constants.dart'; import '../../main.dart'; +import '../Widgets/back_button_to_close.dart'; import '../Widgets/base_card.dart'; import '../Widgets/known_gear.dart'; +import '../Widgets/known_gear_scan_controller.dart'; +import '../Widgets/logging_shake.dart'; +import '../Widgets/snack_bar_overlay.dart'; import '../translation_string_definitions.dart'; import '../utils.dart'; @@ -63,7 +65,7 @@ class _NavigationDrawerExampleState extends ConsumerState { textAlign: TextAlign.center, ), ), - ) + ), ], if (widget.device.mandatoryOtaRequired.value) ...[ BaseCard( elevation: 3, color: Colors.red, child: InkWell( - onTap: () { + onTap: () async { context.push("/ota", extra: widget.device.baseStoredDevice.btMACAddress); }, child: ListTile( @@ -227,38 +230,39 @@ class _ManageGearState extends ConsumerState { ), ), ), - ) + ), ], if (widget.device.hasUpdate.value || HiveProxy.getOrDefault(settings, showDebugging, defaultValue: showDebuggingDefault)) ...[ Padding( padding: const EdgeInsets.all(16.0), child: FilledButton( - onPressed: () { - context.push("/ota", extra: widget.device.baseStoredDevice.btMACAddress); - }, - style: ElevatedButton.styleFrom( - foregroundColor: getTextColor(color), - elevation: 1, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.system_update, - color: getTextColor(color), - ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 4), - ), - Text( - manageDevicesOtaButton(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: getTextColor(color), - ), - ), - ], - )), - ) + onPressed: () async { + context.push("/ota", extra: widget.device.baseStoredDevice.btMACAddress); + }, + style: ElevatedButton.styleFrom( + foregroundColor: getTextColor(color), + elevation: 1, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.system_update, + color: getTextColor(color), + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 4), + ), + Text( + manageDevicesOtaButton(), + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: getTextColor(color), + ), + ), + ], + ), + ), + ), ], Padding( padding: const EdgeInsets.all(16.0), @@ -272,7 +276,7 @@ class _ManageGearState extends ConsumerState { maxLines: 1, maxLength: 30, autocorrect: false, - onSubmitted: (nameValue) { + onSubmitted: (nameValue) async { setState( () { if (nameValue.isNotEmpty) { @@ -296,7 +300,7 @@ class _ManageGearState extends ConsumerState { borderRadius: 22, color: Color(widget.device.baseStoredDevice.color), ), - onTap: () { + onTap: () async { plausible.event(page: "Manage Gear/Gear Color"); showDialog( context: context, @@ -326,7 +330,7 @@ class _ManageGearState extends ConsumerState { child: Text( cancel(), ), - ) + ), ], content: Wrap( children: [ @@ -340,7 +344,7 @@ class _ManageGearState extends ConsumerState { ColorPickerType.accent: true, ColorPickerType.wheel: true, }, - ) + ), ], ), ); @@ -366,9 +370,10 @@ class _ManageGearState extends ConsumerState { LineChartData( titlesData: const FlTitlesData( rightTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: false, - )), + sideTitles: SideTitles( + showTitles: false, + ), + ), topTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), ), @@ -390,7 +395,7 @@ class _ManageGearState extends ConsumerState { ), ), ), - ) + ), ], ); }, @@ -401,7 +406,7 @@ class _ManageGearState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ FilledButton( - onPressed: () { + onPressed: () async { context.push("/settings/developer/console", extra: widget.device); }, child: const Text("Open console"), @@ -595,7 +600,7 @@ class _ManageGearState extends ConsumerState { children: [ if (widget.device.deviceConnectionState.value == ConnectivityState.connected) ...[ TextButton( - onPressed: () { + onPressed: () async { setState(() { widget.device.disableAutoConnect = true; disconnect(widget.device.baseStoredDevice.btMACAddress); @@ -612,7 +617,7 @@ class _ManageGearState extends ConsumerState { Navigator.pop(context); }, child: Text(manageDevicesShutdown()), - ) + ), ], if (widget.device.deviceConnectionState.value == ConnectivityState.disconnected && widget.device.disableAutoConnect) ...[ TextButton( @@ -626,7 +631,7 @@ class _ManageGearState extends ConsumerState { ), ], TextButton( - onPressed: () { + onPressed: () async { setState(() { if (widget.device.deviceConnectionState.value == ConnectivityState.connected) { disconnect(widget.device.baseStoredDevice.btMACAddress); @@ -639,9 +644,9 @@ class _ManageGearState extends ConsumerState { Navigator.pop(context); }, child: Text(manageDevicesForget()), - ) + ), ], - ) + ), ], ), ); diff --git a/lib/Frontend/pages/triggers.dart b/lib/Frontend/pages/triggers.dart index e703cf600..9002ec57b 100644 --- a/lib/Frontend/pages/triggers.dart +++ b/lib/Frontend/pages/triggers.dart @@ -4,18 +4,18 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:multi_value_listenable_builder/multi_value_listenable_builder.dart'; -import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart'; -import 'package:tail_app/Backend/Definitions/Action/base_action.dart'; -import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; -import 'package:tail_app/Backend/sensors.dart'; -import 'package:tail_app/Frontend/utils.dart'; import 'package:uuid/uuid.dart'; +import '../../Backend/Bluetooth/bluetooth_manager.dart'; +import '../../Backend/Definitions/Action/base_action.dart'; +import '../../Backend/Definitions/Device/device_definition.dart'; +import '../../Backend/sensors.dart'; import '../../constants.dart'; import '../../main.dart'; import '../Widgets/device_type_widget.dart'; import '../Widgets/tutorial_card.dart'; import '../translation_string_definitions.dart'; +import '../utils.dart'; import 'action_selector.dart'; class Triggers extends ConsumerStatefulWidget { @@ -75,10 +75,11 @@ class _TriggersState extends ConsumerState { Text( triggersDefSelectSaveLabel(), style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: getTextColor( - Theme.of(context).colorScheme.primary, - )), - ) + color: getTextColor( + Theme.of(context).colorScheme.primary, + ), + ), + ), ], ), ); @@ -87,7 +88,7 @@ class _TriggersState extends ConsumerState { ), title: triggersSelectLabel(), confirmation: true, - onChanged: (value) { + onChanged: (value) async { if (value != null) { setState( () { @@ -122,7 +123,7 @@ class _TriggersState extends ConsumerState { itemBuilder: (BuildContext context, int index) { Trigger trigger = triggersList[index]; return ListTile( - onTap: () { + onTap: () async { showModalBottomSheet( isDismissible: true, isScrollControlled: true, @@ -143,54 +144,56 @@ class _TriggersState extends ConsumerState { }, title: Text(trigger.triggerDefinition!.name), subtitle: MultiValueListenableBuilder( - builder: (BuildContext context, List values, Widget? child) { - return AnimatedCrossFade( - firstChild: Text(trigger.triggerDefinition!.description), - secondChild: MultiValueListenableBuilder( - valueListenables: trigger.actions.map((e) => e.isActiveProgress).toList(), - builder: (context, values, child) { - return TweenAnimationBuilder( - duration: const Duration(milliseconds: 250), - curve: Curves.easeInOut, - tween: Tween( - begin: 0, - end: values.map((e) => e as double).firstWhere( - orElse: () => 0, - (element) { - return element > 0 && element <= 1; - }, - ), + builder: (BuildContext context, List values, Widget? child) { + return AnimatedCrossFade( + firstChild: Text(trigger.triggerDefinition!.description), + secondChild: MultiValueListenableBuilder( + valueListenables: trigger.actions.map((e) => e.isActiveProgress).toList(), + builder: (context, values, child) { + return TweenAnimationBuilder( + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + tween: Tween( + begin: 0, + end: values.map((e) => e as double).firstWhere( + orElse: () => 0, + (element) { + return element > 0 && element <= 1; + }, ), - builder: (context, value, _) => LinearProgressIndicator(value: value), - ); - }, - ), - crossFadeState: !values.any((element) => element == true) ? CrossFadeState.showFirst : CrossFadeState.showSecond, - duration: animationTransitionDuration, - ); - }, - valueListenables: trigger.actions.map((e) => e.isActive).toList()), + ), + builder: (context, value, _) => LinearProgressIndicator(value: value), + ); + }, + ), + crossFadeState: !values.any((element) => element == true) ? CrossFadeState.showFirst : CrossFadeState.showSecond, + duration: animationTransitionDuration, + ); + }, + valueListenables: trigger.actions.map((e) => e.isActive).toList(), + ), leading: ListenableBuilder( listenable: trigger, builder: (BuildContext context, Widget? child) { return Semantics( - label: 'A switch to toggle the trigger ${trigger.triggerDefinition?.name}', - child: Switch( - value: trigger.enabled, - onChanged: (bool value) { - setState( - () { - trigger.enabled = value; - //ref.watch(triggerListProvider.notifier).store(); - }, - ); - }, - )); + label: 'A switch to toggle the trigger ${trigger.triggerDefinition?.name}', + child: Switch( + value: trigger.enabled, + onChanged: (bool value) { + setState( + () { + trigger.enabled = value; + //ref.watch(triggerListProvider.notifier).store(); + }, + ); + }, + ), + ); }, ), ); }, - ) + ), ], ), ), @@ -202,7 +205,7 @@ class TriggerEdit extends ConsumerStatefulWidget { final ScrollController scrollController; final Trigger trigger; - const TriggerEdit({super.key, required this.trigger, required this.scrollController}); + const TriggerEdit({required this.trigger, required this.scrollController, super.key}); @override ConsumerState createState() => _TriggerEditState(); @@ -244,7 +247,7 @@ class _TriggerEditState extends ConsumerState { value: widget.trigger.enabled, onChanged: (bool value) { setState( - () { + () async { widget.trigger.enabled = value; plausible.event(name: "Enable Trigger", props: {"Trigger Type": ref.watch(triggerDefinitionListProvider).where((element) => element.uuid == widget.trigger.triggerDefUUID).first.toString()}); }, @@ -259,7 +262,7 @@ class _TriggerEditState extends ConsumerState { selected: widget.trigger.deviceType, onSelectionChanged: (List value) { setState( - () { + () async { widget.trigger.deviceType = value.toList(); ref.watch(triggerListProvider.notifier).store(); }, @@ -296,26 +299,28 @@ class _TriggerEditState extends ConsumerState { ); }, ), - firstChild: Builder(builder: (context) { - String text = ""; - Iterable knownDevices = ref.read(knownDevicesProvider).values; - for (String actionUUID in e.actions) { - BaseAction? baseAction = ref.watch(getActionFromUUIDProvider(actionUUID)); - if (baseAction != null && - (knownDevices.isEmpty || - knownDevices - .where( - (element) => baseAction.deviceCategory.contains(element.baseDeviceDefinition.deviceType), - ) - .isNotEmpty)) { - if (text.isNotEmpty) { - text += ', '; + firstChild: Builder( + builder: (context) { + String text = ""; + Iterable knownDevices = ref.read(knownDevicesProvider).values; + for (String actionUUID in e.actions) { + BaseAction? baseAction = ref.watch(getActionFromUUIDProvider(actionUUID)); + if (baseAction != null && + (knownDevices.isEmpty || + knownDevices + .where( + (element) => baseAction.deviceCategory.contains(element.baseDeviceDefinition.deviceType), + ) + .isNotEmpty)) { + if (text.isNotEmpty) { + text += ', '; + } + text += baseAction.name; } - text += baseAction.name; } - } - return Text(text.isNotEmpty ? text : triggerActionNotSet()); - }), + return Text(text.isNotEmpty ? text : triggerActionNotSet()); + }, + ), crossFadeState: !value ? CrossFadeState.showFirst : CrossFadeState.showSecond, ); }, @@ -331,17 +336,19 @@ class _TriggerEditState extends ConsumerState { context: context, builder: (BuildContext context) { return Dialog.fullscreen( - backgroundColor: Theme.of(context).canvasColor, - child: ActionSelector( - actionSelectorInfo: ActionSelectorInfo( - deviceType: widget.trigger.deviceType.toSet(), - selectedActions: e.actions - .map( - (e) => ref.read(getActionFromUUIDProvider(e)), - ) - .whereNotNull() - .toList()), - )); + backgroundColor: Theme.of(context).canvasColor, + child: ActionSelector( + actionSelectorInfo: ActionSelectorInfo( + deviceType: widget.trigger.deviceType.toSet(), + selectedActions: e.actions + .map( + (e) => ref.read(getActionFromUUIDProvider(e)), + ) + .whereNotNull() + .toList(), + ), + ), + ); }, ); if (result is List) { @@ -368,7 +375,7 @@ class _TriggerEditState extends ConsumerState { ButtonBar( children: [ TextButton( - onPressed: () { + onPressed: () async { setState( () { ref.watch(triggerListProvider).remove(widget.trigger); @@ -380,7 +387,7 @@ class _TriggerEditState extends ConsumerState { child: const Text("Delete Trigger"), ), ], - ) + ), ], ); } diff --git a/lib/Frontend/pages/view_pdf.dart b/lib/Frontend/pages/view_pdf.dart index 38ea610c0..3da6084fc 100644 --- a/lib/Frontend/pages/view_pdf.dart +++ b/lib/Frontend/pages/view_pdf.dart @@ -6,7 +6,7 @@ import 'package:pdfrx/pdfrx.dart'; class ViewPDF extends StatelessWidget { final Uint8List asset; - const ViewPDF({super.key, required this.asset}); + const ViewPDF({required this.asset, super.key}); @override Widget build(BuildContext context) { diff --git a/lib/Frontend/translation_string_definitions.dart b/lib/Frontend/translation_string_definitions.dart index 78e1fc95c..0c2ac9dca 100644 --- a/lib/Frontend/translation_string_definitions.dart +++ b/lib/Frontend/translation_string_definitions.dart @@ -273,14 +273,23 @@ String otaFailedTitle() => Intl.message("Update Failed. Please restart your gear String otaLowBattery() => Intl.message("Low Battery. Please charge your gear to at least 50%", name: 'otaLowBattery', desc: 'Title for the text that appears when an OTA update was blocked due to low battery'); -String triggerInfoDescription() => Intl.message('Triggers automatically send actions to your gear. You can have multiple triggers active at the same time. Tap on a trigger to edit it, Use the toggle on the left to enable the trigger.', - name: 'triggerInfoDescription', desc: 'Description for what a trigger is and how to use them on the triggers page'); +String triggerInfoDescription() => Intl.message( + 'Triggers automatically send actions to your gear. You can have multiple triggers active at the same time. Tap on a trigger to edit it, Use the toggle on the left to enable the trigger.', + name: 'triggerInfoDescription', + desc: 'Description for what a trigger is and how to use them on the triggers page', + ); -String triggerInfoEditActionDescription() => Intl.message("Tap the pencil to select the Action to play when the event happens. An action will be randomly selected that is compatible with connected gear. GlowTip and Sound actions will trigger alongside Move actions.", - name: 'triggerInfoEditActionDescription', desc: 'Instruction on how to select an action on the trigger edit page'); +String triggerInfoEditActionDescription() => Intl.message( + "Tap the pencil to select the Action to play when the event happens. An action will be randomly selected that is compatible with connected gear. GlowTip and Sound actions will trigger alongside Move actions.", + name: 'triggerInfoEditActionDescription', + desc: 'Instruction on how to select an action on the trigger edit page', + ); -String sequencesInfoDescription() => Intl.message('Custom Actions allow you to make your own Actions for gear. Tapping on a Custom Action will play it. Tap the pencil to edit a Custom Action. Please make sure your gear firmware is up to date.', - name: 'sequencesInfoDescription', desc: 'Description for what a custom action is and how to use them on the Custom Actions page'); +String sequencesInfoDescription() => Intl.message( + 'Custom Actions allow you to make your own Actions for gear. Tapping on a Custom Action will play it. Tap the pencil to edit a Custom Action. Please make sure your gear firmware is up to date.', + name: 'sequencesInfoDescription', + desc: 'Description for what a custom action is and how to use them on the Custom Actions page', + ); String sequencesInfoEditDescription() => Intl.message('Each Custom Action consists of 1-6 moves and may repeat up to 5 times. You can long press a move to re-order it.', name: 'sequencesInfoEditDescription', desc: 'Description for making a custom action on the edit Custom Action page'); @@ -333,11 +342,17 @@ String scanAddDemoGear() => Intl.message("Add Fake Gear", name: 'scanAddDemoGear String scanRemoveDemoGear() => Intl.message("Remove all fake gear", name: 'scanRemoveDemoGear', desc: 'Label for the button to remove all demo gear on the scan for new devices page'); -String scanDemoGearTip() => Intl.message("Want to try out the app but are waiting for your gear to arrive? Add a fake gear. This lets you experience the app as if you had your gear, or if you want to try out gear you currently do not own. This enables a new section on the 'Scan For New Gear' page.", - name: 'scanDemoGearTip', desc: 'Tip Card description for the demo gear on the scan for new devices page'); +String scanDemoGearTip() => Intl.message( + "Want to try out the app but are waiting for your gear to arrive? Add a fake gear. This lets you experience the app as if you had your gear, or if you want to try out gear you currently do not own. This enables a new section on the 'Scan For New Gear' page.", + name: 'scanDemoGearTip', + desc: 'Tip Card description for the demo gear on the scan for new devices page', + ); -String triggerActionSelectorTutorialLabel() => Intl.message("Select as many actions as you want. An action will be randomly selected that is compatible with connected gear. GlowTip and Sound actions will trigger alongside Move actions. Don't forget to save.", - name: 'triggerActionSelectorTutorialLabel', desc: 'Label for the tutorial card on the Action selector for triggers'); +String triggerActionSelectorTutorialLabel() => Intl.message( + "Select as many actions as you want. An action will be randomly selected that is compatible with connected gear. GlowTip and Sound actions will trigger alongside Move actions. Don't forget to save.", + name: 'triggerActionSelectorTutorialLabel', + desc: 'Label for the tutorial card on the Action selector for triggers', + ); String featureLimitedOtaRequiredLabel() => Intl.message("Please update your gear to use this feature.", name: 'featureLimitedOtaRequiredLabel', desc: 'Label for the warning card when a feature requires a firmware update'); diff --git a/lib/Frontend/utils.dart b/lib/Frontend/utils.dart index 606fc9108..3f080c7cb 100644 --- a/lib/Frontend/utils.dart +++ b/lib/Frontend/utils.dart @@ -62,25 +62,27 @@ Future initDio({skipSentry = false}) async { ), ); dio.interceptors.add(LogarteDioInterceptor(logarte)); - dio.interceptors.add(RetryInterceptor( - dio: dio, - logPrint: dioLogger.info, // specify log function (optional) - retries: 15, // retry count (optional) - retryDelays: const [ - // set delays between retries (optional) - Duration(seconds: 1), - Duration(seconds: 2), - Duration(seconds: 3), - Duration(seconds: 4), - Duration(seconds: 5), - Duration(seconds: 10), - Duration(seconds: 20), - Duration(seconds: 40), - Duration(seconds: 80), - Duration(seconds: 160), - Duration(seconds: 320), - ], - )); + dio.interceptors.add( + RetryInterceptor( + dio: dio, + logPrint: dioLogger.info, // specify log function (optional) + retries: 15, // retry count (optional) + retryDelays: const [ + // set delays between retries (optional) + Duration(seconds: 1), + Duration(seconds: 2), + Duration(seconds: 3), + Duration(seconds: 4), + Duration(seconds: 5), + Duration(seconds: 10), + Duration(seconds: 20), + Duration(seconds: 40), + Duration(seconds: 80), + Duration(seconds: 160), + Duration(seconds: 320), + ], + ), + ); if (!skipSentry) { /// This *must* be the last initialization step of the Dio setup, otherwise /// your configuration of Dio might overwrite the Sentry configuration. @@ -104,8 +106,8 @@ Version getVersionSemVer(String input) { if (split.length > 1 && int.tryParse(split[1]) != null) { minor = split[1]; } - if (split.length > 2 && int.tryParse(split[2].replaceAll(RegExp(r'[^0-9]'), '')) != null) { - patch = split[2].replaceAll(RegExp(r'[^0-9]'), ''); + if (split.length > 2 && int.tryParse(split[2].replaceAll(RegExp('[^0-9]'), '')) != null) { + patch = split[2].replaceAll(RegExp('[^0-9]'), ''); } return Version(int.parse(major), int.parse(minor), int.parse(patch)); } diff --git a/lib/main.dart b/lib/main.dart index bd9b1d477..bce8c5f6f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,23 +21,23 @@ import 'package:plausible_analytics/plausible_analytics.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_hive/sentry_hive.dart'; import 'package:sentry_logging/sentry_logging.dart'; -import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; -import 'package:tail_app/Backend/dynamic_config.dart'; -import 'package:tail_app/Frontend/Widgets/bt_app_state_controller.dart'; -import 'package:tail_app/Frontend/utils.dart'; import 'Backend/Definitions/Action/base_action.dart'; +import 'Backend/Definitions/Device/device_definition.dart'; import 'Backend/app_shortcuts.dart'; import 'Backend/audio.dart'; import 'Backend/background_update.dart'; +import 'Backend/dynamic_config.dart'; import 'Backend/favorite_actions.dart'; import 'Backend/logging_wrappers.dart'; import 'Backend/move_lists.dart'; import 'Backend/notifications.dart'; import 'Backend/plausible_dio.dart'; import 'Backend/sensors.dart'; +import 'Frontend/Widgets/bt_app_state_controller.dart'; import 'Frontend/go_router_config.dart'; import 'Frontend/translation_string_definitions.dart'; +import 'Frontend/utils.dart'; import 'constants.dart'; import 'l10n/messages_all_locales.dart'; @@ -116,26 +116,27 @@ Future main() async { mainLogger.info("Detected Environment: $environment"); await SentryFlutter.init( (options) async { - options.dsn = const String.fromEnvironment('SENTRY_DSN', defaultValue: ""); - options.addIntegration(LoggingIntegration()); - options.enableBreadcrumbTrackingForCurrentPlatform(); - options.debug = kDebugMode; - options.diagnosticLevel = SentryLevel.info; - options.environment = environment; - options.tracesSampleRate = dynamicConfigInfo.sentryTraces; - options.profilesSampleRate = dynamicConfigInfo.sentryProfiles; - options.enableDefaultTagsForMetrics = true; - options.attachThreads = true; - options.anrEnabled = true; - options.beforeSend = beforeSend; - options.enableMetrics = true; - options.attachStacktrace = true; - options.attachScreenshot = true; - options.attachViewHierarchy = true; - options.sendClientReports = true; - options.captureFailedRequests = true; - options.enableAutoSessionTracking = true; - options.enableAutoPerformanceTracing = true; + options + ..dsn = const String.fromEnvironment('SENTRY_DSN', defaultValue: "") + ..addIntegration(LoggingIntegration()) + ..enableBreadcrumbTrackingForCurrentPlatform() + ..debug = kDebugMode + ..diagnosticLevel = SentryLevel.info + ..environment = environment + ..tracesSampleRate = dynamicConfigInfo.sentryTraces + ..profilesSampleRate = dynamicConfigInfo.sentryProfiles + ..enableDefaultTagsForMetrics = true + ..attachThreads = true + ..anrEnabled = true + ..beforeSend = beforeSend + ..enableMetrics = true + ..attachStacktrace = true + ..attachScreenshot = true + ..attachViewHierarchy = true + ..sendClientReports = true + ..captureFailedRequests = true + ..enableAutoSessionTracking = true + ..enableAutoPerformanceTracing = true; }, // Init your App. // ignore: missing_provider_scope @@ -151,8 +152,7 @@ Future main() async { } void initFlutter() { - WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); - widgetsBinding.addObserver(WidgetBindingLogger()); + WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized()..addObserver(WidgetBindingLogger()); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); // keeps the splash screen visible } @@ -169,7 +169,7 @@ class WidgetBindingLogger extends WidgetsBindingObserver { } @override - void didChangeLocales(List? locales) async { + Future didChangeLocales(List? locales) async { await initLocale(); } } @@ -245,11 +245,12 @@ class TailApp extends StatefulWidget { mainLogger.info('Starting app'); if (kDebugMode) { mainLogger.info('Debug Mode Enabled'); - HiveProxy.put(settings, showDebugging, true); - HiveProxy.put(settings, allowAnalytics, false); - HiveProxy.put(settings, allowErrorReporting, false); - HiveProxy.put(settings, hasCompletedOnboarding, hasCompletedOnboardingVersionToAgree); - HiveProxy.put(settings, showDemoGear, true); + HiveProxy + ..put(settings, showDebugging, true) + ..put(settings, allowAnalytics, false) + ..put(settings, allowErrorReporting, false) + ..put(settings, hasCompletedOnboarding, hasCompletedOnboardingVersionToAgree) + ..put(settings, showDemoGear, true); } } @@ -261,11 +262,14 @@ class _TailAppState extends State { @override void initState() { // Only after at least the action method is set, the notification events are delivered - AwesomeNotifications().setListeners( + unawaited( + AwesomeNotifications().setListeners( onActionReceivedMethod: NotificationController.onActionReceivedMethod, onNotificationCreatedMethod: NotificationController.onNotificationCreatedMethod, onNotificationDisplayedMethod: NotificationController.onNotificationDisplayedMethod, - onDismissActionReceivedMethod: NotificationController.onDismissActionReceivedMethod); + onDismissActionReceivedMethod: NotificationController.onDismissActionReceivedMethod, + ), + ); super.initState(); } @@ -287,8 +291,8 @@ class _TailAppState extends State { child: ValueListenableBuilder( valueListenable: SentryHive.box(settings).listenable(keys: [appColor]), builder: (BuildContext context, value, Widget? child) { - setupSystemColor(context); - Future(() => FlutterNativeSplash.remove()); //remove the splash screen one frame later + unawaited(setupSystemColor(context)); + Future(FlutterNativeSplash.remove); //remove the splash screen one frame later Color color = Color(HiveProxy.getOrDefault(settings, appColor, defaultValue: appColorDefault)); return MaterialApp.router( title: title(), diff --git a/pubspec.lock b/pubspec.lock index da39ace11..a1567c7b7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -356,13 +356,21 @@ packages: source: hosted version: "1.5.0" custom_lint: - dependency: transitive + dependency: "direct dev" description: name: custom_lint sha256: "22bd87a362f433ba6aae127a7bac2838645270737f3721b180916d7c5946cb5d" url: "https://pub.dev" source: hosted version: "0.5.11" + custom_lint_builder: + dependency: transitive + description: + name: custom_lint_builder + sha256: "0d48e002438950f9582e574ef806b2bea5719d8d14c0f9f754fbad729bcf3b19" + url: "https://pub.dev" + source: hosted + version: "0.5.14" custom_lint_core: dependency: transitive description: @@ -828,6 +836,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e + url: "https://pub.dev" + source: hosted + version: "4.2.0" html: dependency: transitive description: @@ -1429,6 +1445,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0-dev.6" + riverpod_lint: + dependency: "direct dev" + description: + name: riverpod_lint + sha256: a2602c6b7fc34a7e36be63ce6399ed0cab66827b6649ba4382e218df34b72754 + url: "https://pub.dev" + source: hosted + version: "2.3.4" rxdart: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1dc7da30d..b9bed6cd5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -140,7 +140,6 @@ dependencies: dev_dependencies: build_runner: # Required for build flutter_gen_runner: - flutter_lints: # Dryer Lint riverpod_generator: ^3.0.0-dev.6 #required for @Riverpod annotations json_serializable: ^6.8.0 # required for @JsonSerializable annotations hive_generator: ^2.0.1 # required for @HiveType annotations @@ -150,6 +149,10 @@ dev_dependencies: integration_test: sdk: flutter + flutter_lints: # Dryer Lint + custom_lint: + riverpod_lint: + flutter: uses-material-design: true generate: true # Generate app_localizations.dart diff --git a/test/Backend/device_registry_test.dart b/test/Backend/device_registry_test.dart index a6e78900e..dc15ecc1c 100644 --- a/test/Backend/device_registry_test.dart +++ b/test/Backend/device_registry_test.dart @@ -75,40 +75,47 @@ void main() { ); String name = 'MiTail'; String name2 = 'EG2'; - expect(container - .read(knownDevicesProvider) - .length, 0); - expect(HiveProxy - .getAll(devicesBox) - .length, 0); + expect( + container.read(knownDevicesProvider).length, + 0, + ); + expect( + HiveProxy.getAll(devicesBox).length, + 0, + ); BaseStatefulDevice baseStatefulDevice = await createAndStoreGear(name, container); expect(baseStatefulDevice.baseDeviceDefinition.btName, name); - expect(container - .read(knownDevicesProvider) - .length, 1); - expect(container - .read(knownDevicesProvider) - .values - .first, baseStatefulDevice); - expect(HiveProxy - .getAll(devicesBox) - .length, 1); - expect(HiveProxy - .getAll(devicesBox) - .first, baseStatefulDevice.baseStoredDevice); + expect( + container.read(knownDevicesProvider).length, + 1, + ); + expect( + container.read(knownDevicesProvider).values.first, + baseStatefulDevice, + ); + expect( + HiveProxy.getAll(devicesBox).length, + 1, + ); + expect( + HiveProxy.getAll(devicesBox).first, + baseStatefulDevice.baseStoredDevice, + ); BaseStatefulDevice baseStatefulDevice2 = await createAndStoreGear(name2, container); expect(baseStatefulDevice2.baseDeviceDefinition.btName, name2); - expect(container - .read(knownDevicesProvider) - .length, 2); - expect(container - .read(knownDevicesProvider) - .values - .contains(baseStatefulDevice2), true); - expect(HiveProxy - .getAll(devicesBox) - .length, 2); + expect( + container.read(knownDevicesProvider).length, + 2, + ); + expect( + container.read(knownDevicesProvider).values.contains(baseStatefulDevice2), + true, + ); + expect( + HiveProxy.getAll(devicesBox).length, + 2, + ); expect(HiveProxy.getAll(devicesBox).contains(baseStatefulDevice2.baseStoredDevice), true); BaseAction baseAction = BaseAction(name: "name", deviceCategory: [DeviceType.tail], actionCategory: ActionCategory.hidden, uuid: "uuid"); diff --git a/test/testing_utils/bluetooth_test_utils.dart b/test/testing_utils/bluetooth_test_utils.dart index bb849362d..9a9ac3462 100644 --- a/test/testing_utils/bluetooth_test_utils.dart +++ b/test/testing_utils/bluetooth_test_utils.dart @@ -8,7 +8,7 @@ import 'package:tail_app/Backend/Bluetooth/bluetooth_utils.dart'; @GenerateNiceMocks([MockSpec(), MockSpec()]) import 'bluetooth_test_utils.mocks.dart'; -void setupBTMock(String btName, String btMac) { +Future setupBTMock(String btName, String btMac) async { MockFlutterBluePlusMockable flutterBluePlusMock = MockFlutterBluePlusMockable(); flutterBluePlus = flutterBluePlusMock; when(flutterBluePlusMock.isSupported).thenAnswer((_) async => true); @@ -18,10 +18,16 @@ void setupBTMock(String btName, String btMac) { when(flutterBluePlusMock.isSupported).thenAnswer((_) async => true); when(flutterBluePlusMock.isSupported).thenAnswer((_) async => true); when(flutterBluePlusMock.connectedDevices).thenAnswer((_) => [BluetoothDevice(remoteId: DeviceIdentifier(btMac))]); - when(flutterBluePlusMock.scanResults).thenAnswer((_) => - Stream.value([ScanResult(rssi: 50, advertisementData: AdvertisementData(advName: btName, txPowerLevel: null, appearance: null, connectable: true, manufacturerData: {}, serviceData: {}, serviceUuids: []), device: BluetoothDevice(remoteId: DeviceIdentifier(btMac)), timeStamp: DateTime.now())])); - when(flutterBluePlusMock.onScanResults).thenAnswer((_) => - Stream.value([ScanResult(rssi: 50, advertisementData: AdvertisementData(advName: btName, txPowerLevel: null, appearance: null, connectable: true, manufacturerData: {}, serviceData: {}, serviceUuids: []), device: BluetoothDevice(remoteId: DeviceIdentifier(btMac)), timeStamp: DateTime.now())])); + when(flutterBluePlusMock.scanResults).thenAnswer( + (_) => Stream.value( + [ScanResult(rssi: 50, advertisementData: AdvertisementData(advName: btName, txPowerLevel: null, appearance: null, connectable: true, manufacturerData: {}, serviceData: {}, serviceUuids: []), device: BluetoothDevice(remoteId: DeviceIdentifier(btMac)), timeStamp: DateTime.now())], + ), + ); + when(flutterBluePlusMock.onScanResults).thenAnswer( + (_) => Stream.value( + [ScanResult(rssi: 50, advertisementData: AdvertisementData(advName: btName, txPowerLevel: null, appearance: null, connectable: true, manufacturerData: {}, serviceData: {}, serviceUuids: []), device: BluetoothDevice(remoteId: DeviceIdentifier(btMac)), timeStamp: DateTime.now())], + ), + ); when(flutterBluePlusMock.setLogLevel(any)).thenAnswer((_) async {}); BluetoothEvents bluetoothEvents = MockBluetoothEvents(); diff --git a/test/testing_utils/gear_utils.dart b/test/testing_utils/gear_utils.dart index 9e21e6878..244365d50 100644 --- a/test/testing_utils/gear_utils.dart +++ b/test/testing_utils/gear_utils.dart @@ -3,16 +3,15 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:tail_app/Backend/Bluetooth/bluetooth_manager.dart'; import 'package:tail_app/Backend/Bluetooth/bluetooth_manager_plus.dart'; import 'package:tail_app/Backend/Definitions/Device/device_definition.dart'; -import 'package:tail_app/Backend/logging_wrappers.dart'; import 'package:tail_app/Backend/device_registry.dart'; +import 'package:tail_app/Backend/logging_wrappers.dart'; import 'package:tail_app/constants.dart'; Future createAndStoreGear(String gearBtName, ProviderContainer ref, {String gearMacPrefix = 'Dev'}) async { BaseDeviceDefinition baseDeviceDefinition = DeviceRegistry.getByName(gearBtName)!; BaseStoredDevice baseStoredDevice; BaseStatefulDevice statefulDevice; - baseStoredDevice = BaseStoredDevice(baseDeviceDefinition.uuid, "$gearMacPrefix${baseDeviceDefinition.deviceType.name}", baseDeviceDefinition.deviceType.color(ref: ref).value); - baseStoredDevice.name = getNameFromBTName(baseDeviceDefinition.btName); + baseStoredDevice = BaseStoredDevice(baseDeviceDefinition.uuid, "$gearMacPrefix${baseDeviceDefinition.deviceType.name}", baseDeviceDefinition.deviceType.color(ref: ref).value)..name = getNameFromBTName(baseDeviceDefinition.btName); statefulDevice = BaseStatefulDevice(baseDeviceDefinition, baseStoredDevice); statefulDevice.deviceConnectionState.value = ConnectivityState.connected; isAnyGearConnected.value = true;