diff --git a/lib/Backend/Bluetooth/bluetooth_manager_plus.dart b/lib/Backend/Bluetooth/bluetooth_manager_plus.dart index bdc294da..aafc7189 100644 --- a/lib/Backend/Bluetooth/bluetooth_manager_plus.dart +++ b/lib/Backend/Bluetooth/bluetooth_manager_plus.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:math'; import 'package:built_collection/built_collection.dart'; import 'package:collection/collection.dart'; @@ -90,6 +91,8 @@ Future initFlutterBluePlus(InitFlutterBluePlusRef ref) async { //transaction.setTag('Known Device', 'Yes'); } else { baseStoredDevice = BaseStoredDevice(deviceDefinition.uuid, deviceID, deviceDefinition.deviceType.color(ref: ref).value)..name = getNameFromBTName(deviceDefinition.btName); + int code = Random().nextInt(899999) + 100000; + baseStoredDevice.conModePin = code.toString(); statefulDevice = BaseStatefulDevice(deviceDefinition, baseStoredDevice); //transaction.setTag('Known Device', 'No'); Future(() => ref.read(knownDevicesProvider.notifier).add(statefulDevice)); diff --git a/lib/Backend/Definitions/Device/device_definition.dart b/lib/Backend/Definitions/Device/device_definition.dart index 3469a74d..57c08631 100644 --- a/lib/Backend/Definitions/Device/device_definition.dart +++ b/lib/Backend/Definitions/Device/device_definition.dart @@ -344,6 +344,8 @@ class BaseStoredDevice extends ChangeNotifier { int rightHomePosition = 1; @HiveField(12, defaultValue: "") String conModePin = ""; + @HiveField(13, defaultValue: false) + bool conModeEnabled = false; int get color => _color; diff --git a/lib/Frontend/Widgets/manage_gear.dart b/lib/Frontend/Widgets/manage_gear.dart index 3c8e57df..40eef78b 100644 --- a/lib/Frontend/Widgets/manage_gear.dart +++ b/lib/Frontend/Widgets/manage_gear.dart @@ -3,6 +3,7 @@ import 'package:flex_color_picker/flex_color_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:tail_app/Backend/move_lists.dart'; +import 'package:tail_app/Frontend/Widgets/tutorial_card.dart'; import '../../Backend/Bluetooth/bluetooth_manager.dart'; import '../../Backend/Bluetooth/bluetooth_manager_plus.dart'; @@ -185,6 +186,7 @@ class _ManageGearState extends ConsumerState { ManageGearHomePosition(device: device!), ], ManageGearBatteryGraph(device: device!), + ManageGearConventionMode(device: device!), ManageGearDebug(device: device!), ], OverflowBar( @@ -247,6 +249,48 @@ class _ManageGearState extends ConsumerState { } } +class ManageGearConventionMode extends ConsumerWidget { + final BaseStatefulDevice device; + + const ManageGearConventionMode({super.key, required this.device}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return ExpansionTile( + title: Text(manageGearConModeTitle()), + subtitle: Text(manageGearConModeDescription()), + children: [ + PageInfoCard( + text: "Super secret anti hacker power (Insert guide and reset instructions here)", + ), + ListTile( + title: Text(manageGearConModeToggleTitle()), + subtitle: Text("Upon enabling 'Convention Mode' your gear will be rebooted and you will be prompted to enter the pincode. Please view and memorize the pincode before enabling"), + trailing: Switch( + value: device.baseStoredDevice.conModeEnabled, + onChanged: (value) { + //reject if gear disconnected + if (value) { + BluetoothMessage bluetoothMessage = BluetoothMessage(message: "SETPUSSKEY ${device.baseStoredDevice.conModePin}", device: device, timestamp: DateTime.timestamp()); + device.commandQueue.addCommand(bluetoothMessage); + device.baseStoredDevice.conModeEnabled = true; + ref.read(knownDevicesProvider.notifier).store(); + } else { + BluetoothMessage bluetoothMessage = BluetoothMessage(message: "STOPPUSSKEY", device: device, timestamp: DateTime.timestamp()); + device.commandQueue.addCommand(bluetoothMessage); + device.baseStoredDevice.conModeEnabled = false; + ref.read(knownDevicesProvider.notifier).store(); + } + }), + ), + OverflowBar( + children: [FilledButton(onPressed: () => PinCodeRoute(pin: device.baseStoredDevice.conModePin).push(context), child: Text(manageGearConModePincodeTitle()))], + ) + ], + ); + } +} + class ManageGearBatteryGraph extends StatelessWidget { final BaseStatefulDevice device; diff --git a/lib/Frontend/Widgets/pincode_dialog.dart b/lib/Frontend/Widgets/pincode_dialog.dart new file mode 100644 index 00000000..516cf73d --- /dev/null +++ b/lib/Frontend/Widgets/pincode_dialog.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../translation_string_definitions.dart'; + +class PincodeDialog extends StatelessWidget { + const PincodeDialog({super.key, required this.pin}); + + final String pin; + + @override + Widget build(BuildContext context) { + return AlertDialog( + actionsAlignment: MainAxisAlignment.center, + actions: [TextButton(onPressed: () => context.pop(), child: Text(ok()))], + title: Text(manageGearConModePincodeTitle()), + content: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Center( + child: Text( + pin, + style: Theme.of(context).textTheme.titleLarge, + ), + ), + ], + ), + ); + } +} diff --git a/lib/Frontend/Widgets/scan_for_new_device.dart b/lib/Frontend/Widgets/scan_for_new_device.dart index a15b7b06..410e1aa9 100644 --- a/lib/Frontend/Widgets/scan_for_new_device.dart +++ b/lib/Frontend/Widgets/scan_for_new_device.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:animate_do/animate_do.dart'; import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; @@ -198,6 +200,8 @@ class _ScanForNewDevice extends ConsumerState { 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; + int code = Random().nextInt(899999) + 100000; + baseStoredDevice.conModePin = code.toString(); if (!ref.read(knownDevicesProvider).containsKey(baseStoredDevice.btMACAddress)) { ref.read(knownDevicesProvider.notifier).add(statefulDevice); } diff --git a/lib/Frontend/go_router_config.dart b/lib/Frontend/go_router_config.dart index 8407169c..a8f7c55c 100644 --- a/lib/Frontend/go_router_config.dart +++ b/lib/Frontend/go_router_config.dart @@ -15,6 +15,7 @@ import '../Backend/plausible_dio.dart'; import '../constants.dart'; import 'Widgets/color_picker_dialog.dart'; import 'Widgets/manage_gear.dart'; +import 'Widgets/pincode_dialog.dart'; import 'Widgets/scan_for_new_device.dart'; import 'pages/action_selector.dart'; import 'pages/actions.dart'; @@ -121,6 +122,24 @@ class ColorPickerRoute extends GoRouteData { ); } +@TypedGoRoute( + path: '/pincode', + name: 'Pin Code', +) +class PinCodeRoute extends GoRouteData { + const PinCodeRoute({required this.pin}); + + final String pin; + static final GlobalKey $navigatorKey = rootNavigatorKey; + + @override + Page buildPage(BuildContext context, GoRouterState state) => DialogPage( + key: state.pageKey, + name: state.name, + child: PincodeDialog(pin: pin), + ); +} + @TypedGoRoute( path: '/scan', name: 'Scan for new devices', diff --git a/lib/Frontend/translation_string_definitions.dart b/lib/Frontend/translation_string_definitions.dart index fdff9a30..981e872b 100644 --- a/lib/Frontend/translation_string_definitions.dart +++ b/lib/Frontend/translation_string_definitions.dart @@ -390,3 +390,11 @@ String settingsTailBlogWifiOnlyDescription() => Intl.message("Prevent loading Ta String supportTitle() => Intl.message('Support', name: 'supportTitle', desc: 'The label and title of the email support link on the more page'); String supportDescription() => Intl.message('Tap the blue chat icon to contact support', name: 'supportDescription', desc: 'The description of the email support link on the more page'); + +String manageGearConModeTitle() => Intl.message('Convention Mode', name: 'manageGearConModeTitle', desc: 'The title for the convention mode page on the gear page'); + +String manageGearConModeDescription() => Intl.message('Convention Mode is an additional security measure to prevent other devices from connecting to your gear.', name: 'manageGearConModeDescription', desc: 'The description for the convention mode page on the gear page'); + +String manageGearConModePincodeTitle() => Intl.message('View Pin Code', name: 'manageGearConModePincodeTitle', desc: 'The description for the pin mode button on the gear page'); + +String manageGearConModeToggleTitle() => Intl.message('Enable Convention Mode', name: 'manageGearConModeToggleTitle', desc: 'The description for the convention mode enabled button on the gear page');