diff --git a/lib/bluetooth/connection.dart b/lib/bluetooth/connection.dart index 05d4d5d..e51c798 100644 --- a/lib/bluetooth/connection.dart +++ b/lib/bluetooth/connection.dart @@ -163,6 +163,7 @@ class Connection { } void reset() { + _actionStreams.add(LogNotification('Disconnecting all devices')); UniversalBle.stopScan(); isScanning.value = false; for (var device in devices) { diff --git a/lib/bluetooth/devices/base_device.dart b/lib/bluetooth/devices/base_device.dart index e76195b..5f344d2 100644 --- a/lib/bluetooth/devices/base_device.dart +++ b/lib/bluetooth/devices/base_device.dart @@ -21,7 +21,9 @@ import '../messages/notification.dart'; abstract class BaseDevice { final BleDevice scanResult; - BaseDevice(this.scanResult); + final List availableButtons; + + BaseDevice(this.scanResult, {required this.availableButtons}); final zapEncryption = ZapCrypto(LocalKeyProvider()); diff --git a/lib/bluetooth/devices/zwift_click.dart b/lib/bluetooth/devices/zwift_click.dart index 422183c..6903a07 100644 --- a/lib/bluetooth/devices/zwift_click.dart +++ b/lib/bluetooth/devices/zwift_click.dart @@ -5,7 +5,7 @@ import 'package:swift_control/utils/keymap/buttons.dart'; import '../messages/click_notification.dart'; class ZwiftClick extends BaseDevice { - ZwiftClick(super.scanResult); + ZwiftClick(super.scanResult) : super(availableButtons: [ZwiftButton.shiftUpRight, ZwiftButton.shiftDownLeft]); ClickNotification? _lastClickNotification; diff --git a/lib/bluetooth/devices/zwift_play.dart b/lib/bluetooth/devices/zwift_play.dart index e7de68b..aa111a6 100644 --- a/lib/bluetooth/devices/zwift_play.dart +++ b/lib/bluetooth/devices/zwift_play.dart @@ -8,7 +8,25 @@ import 'package:swift_control/utils/keymap/buttons.dart'; import '../ble.dart'; class ZwiftPlay extends BaseDevice { - ZwiftPlay(super.scanResult); + ZwiftPlay(super.scanResult) + : super( + availableButtons: [ + ZwiftButton.y, + ZwiftButton.z, + ZwiftButton.a, + ZwiftButton.b, + ZwiftButton.onOffRight, + ZwiftButton.sideButtonRight, + ZwiftButton.paddleRight, + ZwiftButton.navigationUp, + ZwiftButton.navigationLeft, + ZwiftButton.navigationRight, + ZwiftButton.navigationDown, + ZwiftButton.onOffLeft, + ZwiftButton.sideButtonLeft, + ZwiftButton.paddleLeft, + ], + ); PlayNotification? _lastControllerNotification; diff --git a/lib/bluetooth/devices/zwift_ride.dart b/lib/bluetooth/devices/zwift_ride.dart index de79056..c3c505d 100644 --- a/lib/bluetooth/devices/zwift_ride.dart +++ b/lib/bluetooth/devices/zwift_ride.dart @@ -7,7 +7,29 @@ import 'package:swift_control/utils/keymap/buttons.dart'; import '../ble.dart'; class ZwiftRide extends BaseDevice { - ZwiftRide(super.scanResult); + ZwiftRide(super.scanResult) + : super( + availableButtons: [ + ZwiftButton.navigationLeft, + ZwiftButton.navigationRight, + ZwiftButton.navigationUp, + ZwiftButton.navigationDown, + ZwiftButton.a, + ZwiftButton.b, + ZwiftButton.y, + ZwiftButton.z, + ZwiftButton.shiftUpLeft, + ZwiftButton.shiftDownLeft, + ZwiftButton.shiftUpRight, + ZwiftButton.shiftDownRight, + ZwiftButton.powerUpLeft, + ZwiftButton.powerUpRight, + ZwiftButton.onOffLeft, + ZwiftButton.onOffRight, + ZwiftButton.paddleLeft, + ZwiftButton.paddleRight, + ], + ); @override String get customServiceId => BleUuid.ZWIFT_RIDE_CUSTOM_SERVICE_UUID; diff --git a/lib/pages/device.dart b/lib/pages/device.dart index e0fd5fa..cd457ff 100644 --- a/lib/pages/device.dart +++ b/lib/pages/device.dart @@ -64,10 +64,14 @@ class _DevicePageState extends State { crossAxisAlignment: CrossAxisAlignment.start, spacing: 10, children: [ + Text('Connected Devices:', style: Theme.of(context).textTheme.titleMedium), Text( - 'Devices:\n${connection.devices.joinToString(separator: '\n', transform: (it) { - return "${it.device.name ?? it.runtimeType}: ${it.isConnected ? 'Connected' : 'Not connected'}${it.batteryLevel != null ? ' - Battery Level: ${it.batteryLevel}%' : ''}"; - })}', + connection.devices.joinToString( + separator: '\n', + transform: (it) { + return "${it.device.name ?? it.runtimeType}: ${it.isConnected ? 'Connected' : 'Not connected'}${it.batteryLevel != null ? ' - Battery Level: ${it.batteryLevel}%' : ''}"; + }, + ), ), Divider(color: Theme.of(context).colorScheme.primary, height: 30), if (!kIsWeb) @@ -78,6 +82,7 @@ class _DevicePageState extends State { children: [ Flex( mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, direction: MediaQuery.sizeOf(context).width > 600 ? Axis.horizontal : Axis.vertical, spacing: 8, children: [ @@ -87,7 +92,7 @@ class _DevicePageState extends State { SupportedApp.supportedApps .map((app) => DropdownMenuEntry(value: app, label: app.name)) .toList(), - label: Text('Select Keymap'), + label: Text('Select Keymap / app'), onSelected: (app) async { if (app == null) { return; @@ -110,39 +115,40 @@ class _DevicePageState extends State { hintText: 'Select your Keymap', ), - ElevatedButton( - onPressed: () async { - if (actionHandler.supportedApp! is! CustomApp) { - final customApp = CustomApp(); + if (actionHandler.supportedApp != null) + ElevatedButton( + onPressed: () async { + if (actionHandler.supportedApp! is! CustomApp) { + final customApp = CustomApp(); - actionHandler.supportedApp!.keymap.keyPairs.forEachIndexed((pair, index) { - pair.buttons.forEachIndexed((button, indexB) { - customApp.setKey( - button, - physicalKey: pair.physicalKey!, - logicalKey: pair.logicalKey, - isLongPress: pair.isLongPress, - touchPosition: - pair.touchPosition != Offset.zero - ? pair.touchPosition - : Offset(((indexB + 1)) * 100, 200 + (index * 100)), - ); + actionHandler.supportedApp!.keymap.keyPairs.forEachIndexed((pair, index) { + pair.buttons.forEachIndexed((button, indexB) { + customApp.setKey( + button, + physicalKey: pair.physicalKey!, + logicalKey: pair.logicalKey, + isLongPress: pair.isLongPress, + touchPosition: + pair.touchPosition != Offset.zero + ? pair.touchPosition + : Offset(((indexB + 1)) * 100, 200 + (index * 100)), + ); + }); }); - }); - actionHandler.supportedApp = customApp; - settings.setApp(customApp); - } - final result = await Navigator.of( - context, - ).push(MaterialPageRoute(builder: (_) => TouchAreaSetupPage())); - if (result == true && actionHandler.supportedApp is CustomApp) { - settings.setApp(actionHandler.supportedApp!); - } - setState(() {}); - }, - child: Text('Customize Keymap'), - ), + actionHandler.supportedApp = customApp; + settings.setApp(customApp); + } + final result = await Navigator.of( + context, + ).push(MaterialPageRoute(builder: (_) => TouchAreaSetupPage())); + if (result == true && actionHandler.supportedApp is CustomApp) { + settings.setApp(actionHandler.supportedApp!); + } + setState(() {}); + }, + child: Text('Customize Keymap'), + ), ], ), if (actionHandler.supportedApp != null) diff --git a/lib/pages/requirements.dart b/lib/pages/requirements.dart index b980f18..a1843e5 100644 --- a/lib/pages/requirements.dart +++ b/lib/pages/requirements.dart @@ -72,55 +72,68 @@ class _RequirementsPageState extends State with WidgetsBinding body: _requirements.isEmpty ? Center(child: CircularProgressIndicator()) - : Stepper( - currentStep: _currentStep, - connectorColor: WidgetStateProperty.resolveWith( - (Set states) => Theme.of(context).colorScheme.primary, - ), - onStepContinue: - _currentStep < _requirements.length - ? () { - setState(() { - _currentStep += 1; - }); + : Column( + children: [ + Padding( + padding: const EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0), + child: Text( + 'Please complete the following requirements to make the app work correctly:', + style: Theme.of(context).textTheme.titleMedium, + ), + ), + Expanded( + child: Stepper( + currentStep: _currentStep, + connectorColor: WidgetStateProperty.resolveWith( + (Set states) => Theme.of(context).colorScheme.primary, + ), + onStepContinue: + _currentStep < _requirements.length + ? () { + setState(() { + _currentStep += 1; + }); + } + : null, + onStepTapped: (step) { + if (_requirements[step].status) { + return; } - : null, - onStepTapped: (step) { - if (_requirements[step].status) { - return; - } - final hasEarlierIncomplete = _requirements.indexWhere((req) => !req.status) < step; - if (hasEarlierIncomplete) { - return; - } - setState(() { - _currentStep = step; - }); - }, - controlsBuilder: (context, details) => Container(), - steps: - _requirements - .mapIndexed( - (index, req) => Step( - title: Text(req.name), - content: Container( - padding: const EdgeInsets.symmetric(vertical: 16.0), - alignment: Alignment.centerLeft, - child: - (index == _currentStep - ? req.build(context, () { - _reloadRequirements(); - }) - : null) ?? - ElevatedButton( - onPressed: req.status ? null : () => _callRequirement(req), - child: Text(req.name), + final hasEarlierIncomplete = _requirements.indexWhere((req) => !req.status) < step; + if (hasEarlierIncomplete) { + return; + } + setState(() { + _currentStep = step; + }); + }, + controlsBuilder: (context, details) => Container(), + steps: + _requirements + .mapIndexed( + (index, req) => Step( + title: Text(req.name), + content: Container( + padding: const EdgeInsets.symmetric(vertical: 16.0), + alignment: Alignment.centerLeft, + child: + (index == _currentStep + ? req.build(context, () { + _reloadRequirements(); + }) + : null) ?? + ElevatedButton( + onPressed: req.status ? null : () => _callRequirement(req), + child: Text(req.name), + ), ), - ), - state: req.status ? StepState.complete : StepState.indexed, - ), - ) - .toList(), + state: req.status ? StepState.complete : StepState.indexed, + ), + ) + .toList(), + ), + ), + ], ), ); } diff --git a/lib/pages/scan.dart b/lib/pages/scan.dart index 20b0d4e..5adcfd2 100644 --- a/lib/pages/scan.dart +++ b/lib/pages/scan.dart @@ -45,9 +45,7 @@ class _ScanWidgetState extends State { Widget build(BuildContext context) { return Container( constraints: BoxConstraints(minHeight: 200), - child: ListView( - padding: EdgeInsets.all(16), - shrinkWrap: true, + child: Column( children: [ ValueListenableBuilder( valueListenable: connection.isScanning, diff --git a/lib/utils/requirements/multi.dart b/lib/utils/requirements/multi.dart index f627de0..99af4da 100644 --- a/lib/utils/requirements/multi.dart +++ b/lib/utils/requirements/multi.dart @@ -46,7 +46,7 @@ class UnsupportedPlatform extends PlatformRequirement { } class BluetoothScanning extends PlatformRequirement { - BluetoothScanning() : super('Bluetooth Scanning') { + BluetoothScanning() : super('Finding your Zwift® controller...') { status = false; } diff --git a/lib/widgets/keymap_explanation.dart b/lib/widgets/keymap_explanation.dart index a82bfc4..350256d 100644 --- a/lib/widgets/keymap_explanation.dart +++ b/lib/widgets/keymap_explanation.dart @@ -10,10 +10,16 @@ class KeymapExplanation extends StatelessWidget { @override Widget build(BuildContext context) { - final keyboardGroups = keymap.keyPairs + final connectedDevice = connection.devices.firstOrNull; + + final availableKeypairs = keymap.keyPairs.filter( + (e) => connectedDevice?.availableButtons.containsAny(e.buttons) == true, + ); + + final keyboardGroups = availableKeypairs .filter((e) => e.physicalKey != null) .groupBy((element) => '${element.physicalKey}-${element.isLongPress}'); - final touchGroups = keymap.keyPairs + final touchGroups = availableKeypairs .filter((e) => e.physicalKey == null && e.touchPosition != Offset.zero) .groupBy((element) => '${element.touchPosition}-${element.isLongPress}'); @@ -33,7 +39,7 @@ class KeymapExplanation extends StatelessWidget { Padding( padding: const EdgeInsets.all(6), child: Text( - 'Button on your ${connection.devices.firstOrNull?.device.name ?? connection.devices.firstOrNull?.runtimeType}', + 'Button on your ${connectedDevice?.device.name ?? connectedDevice?.runtimeType}', style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold), ), ), @@ -57,7 +63,8 @@ class KeymapExplanation extends StatelessWidget { children: [ for (final keyPair in pair.value) for (final button in keyPair.buttons) - IntrinsicWidth(child: _KeyWidget(label: button.name.splitByUpperCase())), + if (connectedDevice?.availableButtons.contains(button) == true) + IntrinsicWidth(child: _KeyWidget(label: button.name.splitByUpperCase())), ], ), ), @@ -84,7 +91,9 @@ class KeymapExplanation extends StatelessWidget { spacing: 8, children: [ for (final keyPair in pair.value) - for (final button in keyPair.buttons) _KeyWidget(label: button.name.splitByUpperCase()), + for (final button in keyPair.buttons) + if (connectedDevice?.availableButtons.contains(button) == true) + _KeyWidget(label: button.name.splitByUpperCase()), ], ), ), diff --git a/lib/widgets/logviewer.dart b/lib/widgets/logviewer.dart index de7adb8..1a280d3 100644 --- a/lib/widgets/logviewer.dart +++ b/lib/widgets/logviewer.dart @@ -48,52 +48,45 @@ class _LogviewerState extends State { @override Widget build(BuildContext context) { - return Stack( - children: [ - SelectionArea( - child: ListView( - controller: _scrollController, - children: - _actions - .map( - (action) => Text.rich( - TextSpan( - children: [ - TextSpan( - text: action.date.toString().split(" ").last, - style: TextStyle( - fontSize: 12, - fontFeatures: [FontFeature.tabularFigures()], - fontFamily: "monospace", - fontFamilyFallback: ["Courier"], - ), - ), - TextSpan( - text: " ${action.entry}", - style: TextStyle( - fontSize: 12, - fontFeatures: [FontFeature.tabularFigures()], - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ) - .toList(), + return SelectionArea( + child: ListView( + controller: _scrollController, + children: [ + ..._actions.map( + (action) => Text.rich( + TextSpan( + children: [ + TextSpan( + text: action.date.toString().split(" ").last, + style: TextStyle( + fontSize: 12, + fontFeatures: [FontFeature.tabularFigures()], + fontFamily: "monospace", + fontFamilyFallback: ["Courier"], + ), + ), + TextSpan( + text: " ${action.entry}", + style: TextStyle( + fontSize: 12, + fontFeatures: [FontFeature.tabularFigures()], + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), ), - ), - Align( - alignment: Alignment.topRight, - child: IconButton( + + TextButton( onPressed: () { _actions.clear(); setState(() {}); }, - icon: Icon(Icons.clear), + child: Text('Clear Log'), ), - ), - ], + ], + ), ); } } diff --git a/lib/widgets/menu.dart b/lib/widgets/menu.dart index 375eef6..4e9361a 100644 --- a/lib/widgets/menu.dart +++ b/lib/widgets/menu.dart @@ -26,7 +26,7 @@ List buildMenuButtons() { launchUrlString(link); }, ), - if (!kIsWeb && Platform.isAndroid && !isFromPlayStore && false) + if (!kIsWeb && Platform.isAndroid && !isFromPlayStore) PopupMenuItem( child: Text('by buying the app from Play Store'), onTap: () { diff --git a/playstoreassets/mob1.jpg b/playstoreassets/mob1.jpg deleted file mode 100644 index 343c7ae..0000000 Binary files a/playstoreassets/mob1.jpg and /dev/null differ diff --git a/playstoreassets/mob1.png b/playstoreassets/mob1.png new file mode 100644 index 0000000..8822fae Binary files /dev/null and b/playstoreassets/mob1.png differ diff --git a/playstoreassets/mob2.jpg b/playstoreassets/mob2.jpg deleted file mode 100644 index ac10fce..0000000 Binary files a/playstoreassets/mob2.jpg and /dev/null differ diff --git a/playstoreassets/mob2.png b/playstoreassets/mob2.png new file mode 100644 index 0000000..48847c9 Binary files /dev/null and b/playstoreassets/mob2.png differ diff --git a/playstoreassets/tab1.jpg b/playstoreassets/tab1.jpg deleted file mode 100644 index b0267f0..0000000 Binary files a/playstoreassets/tab1.jpg and /dev/null differ diff --git a/playstoreassets/tab1.png b/playstoreassets/tab1.png new file mode 100644 index 0000000..a740231 Binary files /dev/null and b/playstoreassets/tab1.png differ diff --git a/playstoreassets/tab2.jpg b/playstoreassets/tab2.jpg deleted file mode 100644 index 842c2c1..0000000 Binary files a/playstoreassets/tab2.jpg and /dev/null differ diff --git a/playstoreassets/tab2.png b/playstoreassets/tab2.png new file mode 100644 index 0000000..9e4cb3c Binary files /dev/null and b/playstoreassets/tab2.png differ