mirror of
https://github.com/jonasbark/swiftcontrol.git
synced 2026-02-18 00:17:40 +01:00
bugfixes and clarifications
This commit is contained in:
@@ -52,8 +52,8 @@ Best follow our landing page and the "Get Started" button: [bikecontrol.app](htt
|
||||
- CYCPLUS BC2 Virtual Shifter
|
||||
- Elite Sterzo Smart (for steering support)
|
||||
- Elite Square Smart Frame (beta)
|
||||
- Gyroscope/Accelerometer Steering (for mobile devices)
|
||||
- Mount your phone on the handlebar for steering detection
|
||||
- Your Phone!
|
||||
- Mount your phone on the handlebar to detect e.g. steering
|
||||
- Available on Android and iOS
|
||||
- Gamepads
|
||||
- Keyboard input
|
||||
|
||||
@@ -52,25 +52,25 @@ abstract class BaseDevice {
|
||||
Future<void> connect();
|
||||
|
||||
Future<void> handleButtonsClickedWithoutLongPressSupport(List<ControllerButton> clickedButtons) async {
|
||||
await handleButtonsClicked(clickedButtons);
|
||||
await handleButtonsClicked(clickedButtons, longPress: true);
|
||||
if (clickedButtons.length == 1) {
|
||||
final keyPair = core.actionHandler.supportedApp?.keymap.getKeyPair(clickedButtons.single);
|
||||
if (keyPair != null && (keyPair.isLongPress || keyPair.inGameAction?.isLongPress == true)) {
|
||||
// simulate release after click
|
||||
_longPressTimer?.cancel();
|
||||
await Future.delayed(const Duration(milliseconds: 800));
|
||||
await handleButtonsClicked([]);
|
||||
await handleButtonsClicked([], longPress: true);
|
||||
} else {
|
||||
await handleButtonsClicked([]);
|
||||
await handleButtonsClicked([], longPress: true);
|
||||
}
|
||||
} else {
|
||||
await handleButtonsClicked([]);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> handleButtonsClicked(List<ControllerButton>? buttonsClicked) async {
|
||||
Future<void> handleButtonsClicked(List<ControllerButton>? buttonsClicked, {bool longPress = false}) async {
|
||||
try {
|
||||
await _handleButtonsClickedInternal(buttonsClicked);
|
||||
await _handleButtonsClickedInternal(buttonsClicked, longPress: longPress);
|
||||
} catch (e, st) {
|
||||
actionStreamInternal.add(
|
||||
LogNotification('Error handling button clicks: $e\n$st'),
|
||||
@@ -78,7 +78,7 @@ abstract class BaseDevice {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleButtonsClickedInternal(List<ControllerButton>? buttonsClicked) async {
|
||||
Future<void> _handleButtonsClickedInternal(List<ControllerButton>? buttonsClicked, {required bool longPress}) async {
|
||||
if (buttonsClicked == null) {
|
||||
// ignore, no changes
|
||||
} else if (buttonsClicked.isEmpty) {
|
||||
@@ -88,8 +88,9 @@ abstract class BaseDevice {
|
||||
// Handle release events for long press keys
|
||||
final buttonsReleased = _previouslyPressedButtons.toList();
|
||||
final isLongPress =
|
||||
longPress ||
|
||||
buttonsReleased.singleOrNull != null &&
|
||||
core.actionHandler.supportedApp?.keymap.getKeyPair(buttonsReleased.single)?.isLongPress == true;
|
||||
core.actionHandler.supportedApp?.keymap.getKeyPair(buttonsReleased.single)?.isLongPress == true;
|
||||
if (buttonsReleased.isNotEmpty && isLongPress) {
|
||||
await performRelease(buttonsReleased);
|
||||
}
|
||||
@@ -100,15 +101,17 @@ abstract class BaseDevice {
|
||||
// Handle release events for buttons that are no longer pressed
|
||||
final buttonsReleased = _previouslyPressedButtons.difference(buttonsClicked.toSet()).toList();
|
||||
final wasLongPress =
|
||||
longPress ||
|
||||
buttonsReleased.singleOrNull != null &&
|
||||
core.actionHandler.supportedApp?.keymap.getKeyPair(buttonsReleased.single)?.isLongPress == true;
|
||||
core.actionHandler.supportedApp?.keymap.getKeyPair(buttonsReleased.single)?.isLongPress == true;
|
||||
if (buttonsReleased.isNotEmpty && wasLongPress) {
|
||||
await performRelease(buttonsReleased);
|
||||
}
|
||||
|
||||
final isLongPress =
|
||||
longPress ||
|
||||
buttonsClicked.singleOrNull != null &&
|
||||
core.actionHandler.supportedApp?.keymap.getKeyPair(buttonsClicked.single)?.isLongPress == true;
|
||||
core.actionHandler.supportedApp?.keymap.getKeyPair(buttonsClicked.single)?.isLongPress == true;
|
||||
|
||||
if (!isLongPress &&
|
||||
!(buttonsClicked.singleOrNull == ZwiftButtons.onOffLeft ||
|
||||
@@ -201,6 +204,9 @@ abstract class BaseDevice {
|
||||
Widget showInformation(BuildContext context);
|
||||
|
||||
ControllerButton getOrAddButton(String key, ControllerButton Function() creator) {
|
||||
if (core.actionHandler.supportedApp == null) {
|
||||
return creator();
|
||||
}
|
||||
if (core.actionHandler.supportedApp is! CustomApp) {
|
||||
final currentProfile = core.actionHandler.supportedApp!.name;
|
||||
// should we display this to the user?
|
||||
|
||||
@@ -53,8 +53,7 @@ class SramAxs extends BluetoothDevice {
|
||||
|
||||
void _emitClick(ControllerButton button) {
|
||||
// Use the common pipeline so long-press handling and app action execution stays consistent.
|
||||
handleButtonsClicked([button]);
|
||||
handleButtonsClicked([]);
|
||||
handleButtonsClickedWithoutLongPressSupport([button]);
|
||||
}
|
||||
|
||||
void _registerTap() {
|
||||
@@ -117,10 +116,10 @@ class SramAxs extends BluetoothDevice {
|
||||
Text(
|
||||
"Unfortunately, at the moment it's not possible to determine which physical button was pressed on your SRAM AXS device. Let us know if you have a contact at SRAM who can help :)\n\n"
|
||||
'So the app exposes two logical buttons:\n'
|
||||
'• SRAM Action (Single Click), assigned to Shift Up\n'
|
||||
'• SRAM Action (Double Click), assigned to Shift Down\n\n'
|
||||
'• SRAM Tap, assigned to Shift Up\n'
|
||||
'• SRAM Double Tap, assigned to Shift Down\n\n'
|
||||
'You can assign an action to each in the app settings.',
|
||||
).small,
|
||||
).xSmall,
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return PrimaryButton(
|
||||
|
||||
@@ -380,7 +380,7 @@ class FtmsMdnsEmulator extends TrainerConnection {
|
||||
_write(_socket!, zero);
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('Sent action ${keyPair.inGameAction!.title} to Zwift Emulator');
|
||||
print('Sent action $isKeyUp vs $isKeyDown ${keyPair.inGameAction!.title} to Zwift Emulator');
|
||||
}
|
||||
return Success('Sent action: ${keyPair.inGameAction!.title}');
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:bike_control/bluetooth/devices/bluetooth_device.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/constants.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pbenum.dart';
|
||||
@@ -9,6 +7,8 @@ import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/keymap/buttons.dart';
|
||||
import 'package:bike_control/utils/single_line_exception.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
abstract class ZwiftDevice extends BluetoothDevice {
|
||||
@@ -152,10 +152,10 @@ abstract class ZwiftDevice extends BluetoothDevice {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleButtonsClicked(List<ControllerButton>? buttonsClicked) async {
|
||||
Future<void> handleButtonsClicked(List<ControllerButton>? buttonsClicked, {bool longPress = false}) async {
|
||||
// the same messages are sent multiple times, so ignore
|
||||
if (_lastButtonsClicked == null || _lastButtonsClicked?.contentEquals(buttonsClicked ?? []) == false) {
|
||||
super.handleButtonsClicked(buttonsClicked);
|
||||
super.handleButtonsClicked(buttonsClicked, longPress: longPress);
|
||||
}
|
||||
_lastButtonsClicked = buttonsClicked;
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@
|
||||
"enableMediaKeyDetection": "Medientastenerkennung aktivieren",
|
||||
"enablePairingProcess": "Kopplungsprozess aktivieren",
|
||||
"enablePermissions": "Berechtigungen aktivieren",
|
||||
"enableSteeringWithPhone": "Sensoren Ihres Telefons aktivieren z.B. zum Lenken",
|
||||
"enableVibrationFeedback": "Vibrationsfeedback beim Gangwechsel aktivieren",
|
||||
"enableZwiftControllerBluetooth": "Zwift Controller aktivieren (Bluetooth)",
|
||||
"enableZwiftControllerNetwork": "Zwift Controller aktivieren (Netzwerk)",
|
||||
|
||||
@@ -144,6 +144,7 @@
|
||||
"enableMediaKeyDetection": "Enable Media Key Detection",
|
||||
"enablePairingProcess": "Enable Pairing Process",
|
||||
"enablePermissions": "Enable Permissions",
|
||||
"enableSteeringWithPhone": "Enable Phones' sensors to enable e.g. steering",
|
||||
"enableVibrationFeedback": "Enable vibration feedback when shifting gears",
|
||||
"enableZwiftControllerBluetooth": "Enable Zwift Controller (Bluetooth)",
|
||||
"enableZwiftControllerNetwork": "Enable Zwift Controller (Network)",
|
||||
@@ -421,6 +422,5 @@
|
||||
"whatsNew": "What's New",
|
||||
"whyPermissionNeeded": "Why is this permission needed?",
|
||||
"zwiftControllerAction": "Zwift Controller Action",
|
||||
"zwiftControllerDescription": "Enables BikeControl to act as a Zwift-compatible controller.",
|
||||
"enableSteeringWithPhone": "Enable Steering using your phone's sensors"
|
||||
}
|
||||
"zwiftControllerDescription": "Enables BikeControl to act as a Zwift-compatible controller."
|
||||
}
|
||||
@@ -144,6 +144,7 @@
|
||||
"enableMediaKeyDetection": "Activer la détection des touches multimédias",
|
||||
"enablePairingProcess": "Activer le processus d'appairage",
|
||||
"enablePermissions": "Activer les autorisations",
|
||||
"enableSteeringWithPhone": "Activez les capteurs du téléphone pour permettre, par exemple, la direction.",
|
||||
"enableVibrationFeedback": "Activer le retour haptique par vibration lors du changement de vitesse",
|
||||
"enableZwiftControllerBluetooth": "Activer le contrôleur Zwift (Bluetooth)",
|
||||
"enableZwiftControllerNetwork": "Activer le contrôleur Zwift (réseau)",
|
||||
|
||||
@@ -144,6 +144,7 @@
|
||||
"enableMediaKeyDetection": "Włącz rozpoznawanie klawiszy multimedialnych",
|
||||
"enablePairingProcess": "Włącz proces parowania",
|
||||
"enablePermissions": "Nadaj uprawnienia",
|
||||
"enableSteeringWithPhone": "Włącz czujniki telefonów, aby umożliwić np. sterowanie",
|
||||
"enableVibrationFeedback": "Włącz wibracje podczas zmiany biegów",
|
||||
"enableZwiftControllerBluetooth": "Włącz kontroler Zwift (Bluetooth)",
|
||||
"enableZwiftControllerNetwork": "Włącz kontroler Zwift (sieć)",
|
||||
@@ -163,7 +164,7 @@
|
||||
"firmware": "Firmware",
|
||||
"forceCloseToUpdate": "Wymuś zamknięcie aplikacji, aby móc korzystać z nowej wersji",
|
||||
"fullVersion": "Pełna wersja",
|
||||
"fullVersionDescription": "The full version includes:\n- Unlimited commands per day\n- Access to all future updates\n- No subscription! A one-time fee only :)",
|
||||
"fullVersionDescription": "Pełna wersja zawiera: \n- Nielimitowane polecenia dziennie \n- Dostęp do wszystkich przyszłych aktualizacji \n- Brak subskrypcji! Opłata jednorazowa :)",
|
||||
"getSupport": "Uzyskaj wsparcie",
|
||||
"gotIt": "Zrozumiałem!",
|
||||
"grant": "Nadaj",
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:async';
|
||||
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/main.dart';
|
||||
import 'package:bike_control/pages/button_simulator.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/i18n_extension.dart';
|
||||
import 'package:bike_control/utils/iap/iap_manager.dart';
|
||||
@@ -12,7 +11,6 @@ import 'package:bike_control/widgets/ui/colored_title.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
|
||||
import '../bluetooth/devices/base_device.dart';
|
||||
import '../widgets/ignored_devices_dialog.dart';
|
||||
|
||||
class DevicePage extends StatefulWidget {
|
||||
final VoidCallback onUpdate;
|
||||
@@ -96,18 +94,6 @@ class _DevicePageState extends State<DevicePage> with WidgetsBindingObserver {
|
||||
),
|
||||
],
|
||||
|
||||
if (core.settings.getIgnoredDevices().isNotEmpty)
|
||||
OutlineButton(
|
||||
child: Text(context.i18n.manageIgnoredDevices),
|
||||
onPressed: () async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => IgnoredDevicesDialog(),
|
||||
);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
|
||||
SizedBox(),
|
||||
if (core.connection.controllerDevices.isNotEmpty)
|
||||
Row(
|
||||
@@ -121,18 +107,6 @@ class _DevicePageState extends State<DevicePage> with WidgetsBindingObserver {
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
else
|
||||
PrimaryButton(
|
||||
child: Text(AppLocalizations.of(context).noControllerUseCompanionMode),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (c) => ButtonSimulator(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -135,6 +135,7 @@ class _NavigationState extends State<Navigation> {
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
trailing: buildMenuButtons(
|
||||
context,
|
||||
_selectedPage,
|
||||
_isMobile
|
||||
? () {
|
||||
setState(() {
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/main.dart';
|
||||
import 'package:bike_control/pages/button_simulator.dart';
|
||||
import 'package:bike_control/pages/configuration.dart';
|
||||
import 'package:bike_control/pages/navigation.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/i18n_extension.dart';
|
||||
import 'package:bike_control/utils/iap/iap_manager.dart';
|
||||
@@ -97,7 +98,7 @@ class _TrainerPageState extends State<TrainerPage> with WidgetsBindingObserver {
|
||||
controller: _scrollController,
|
||||
child: SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
padding: EdgeInsets.symmetric(vertical: 32, horizontal: 16),
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -152,6 +153,21 @@ class _TrainerPageState extends State<TrainerPage> with WidgetsBindingObserver {
|
||||
),
|
||||
if (core.logic.showLocalControl && !showLocalAsOther) LocalTile(),
|
||||
if (core.logic.showMyWhooshLink && !showWhooshLinkAsOther) MyWhooshLinkTile(),
|
||||
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: '${context.i18n.needHelpClickHelp} '),
|
||||
WidgetSpan(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: Icon(Icons.help_outline),
|
||||
),
|
||||
),
|
||||
TextSpan(text: ' ${context.i18n.needHelpDontHesitate}'),
|
||||
],
|
||||
),
|
||||
).small.muted,
|
||||
if (core.logic.showRemote || showLocalAsOther || showWhooshLinkAsOther) ...[
|
||||
SizedBox(height: 16),
|
||||
Accordion(
|
||||
@@ -171,21 +187,6 @@ class _TrainerPageState extends State<TrainerPage> with WidgetsBindingObserver {
|
||||
],
|
||||
|
||||
SizedBox(height: 4),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: '${context.i18n.needHelpClickHelp} '),
|
||||
WidgetSpan(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: Icon(Icons.help_outline),
|
||||
),
|
||||
),
|
||||
TextSpan(text: ' ${context.i18n.needHelpDontHesitate}'),
|
||||
],
|
||||
),
|
||||
).small.muted,
|
||||
SizedBox(),
|
||||
Flex(
|
||||
direction: isMobile ? Axis.vertical : Axis.horizontal,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@@ -193,6 +194,7 @@ class _TrainerPageState extends State<TrainerPage> with WidgetsBindingObserver {
|
||||
spacing: 8,
|
||||
children: [
|
||||
PrimaryButton(
|
||||
leading: Icon(Icons.computer_outlined),
|
||||
child: Text(
|
||||
AppLocalizations.of(
|
||||
context,
|
||||
@@ -217,10 +219,11 @@ class _TrainerPageState extends State<TrainerPage> with WidgetsBindingObserver {
|
||||
},
|
||||
),
|
||||
PrimaryButton(
|
||||
child: Text(context.i18n.adjustControllerButtons),
|
||||
leading: Icon(BCPage.customization.icon),
|
||||
onPressed: () {
|
||||
widget.goToNextPage();
|
||||
},
|
||||
child: Text(context.i18n.adjustControllerButtons),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -160,8 +160,8 @@ class IAPManager {
|
||||
_windowsIapService?.dispose();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
void reset(bool fullReset) {
|
||||
_windowsIapService?.reset();
|
||||
_iapService?.reset();
|
||||
_iapService?.reset(fullReset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,9 @@ class IAPService {
|
||||
onDone: () => _subscription?.cancel(),
|
||||
onError: (error) {
|
||||
debugPrint('IAP Error: $error');
|
||||
core.connection.signalNotification(
|
||||
LogNotification('There was an error with in-app purchases: ${error.toString()}'),
|
||||
);
|
||||
// On error, default to allowing access
|
||||
IAPManager.instance.isPurchased.value = false;
|
||||
},
|
||||
@@ -189,6 +192,9 @@ class IAPService {
|
||||
}
|
||||
|
||||
final purchasedVersion = json['receipt']["original_application_version"];
|
||||
core.connection.signalNotification(
|
||||
LogNotification('Apple receipt validated for version: $purchasedVersion'),
|
||||
);
|
||||
IAPManager.instance.isPurchased.value = Version.parse(purchasedVersion) < Version(4, 2, 0);
|
||||
if (IAPManager.instance.isPurchased.value) {
|
||||
debugPrint('Apple receipt validation successful - granting full access');
|
||||
@@ -218,8 +224,9 @@ class IAPService {
|
||||
}
|
||||
debugPrint('Existing Android user detected - granting full access');
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e, s) {
|
||||
debugPrint('Error checking Android previous purchase: $e');
|
||||
recordError(e, s, context: 'Checking Android previous purchase');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +247,9 @@ class IAPService {
|
||||
/// Handle purchase updates
|
||||
Future<void> _onPurchaseUpdate(List<PurchaseDetails> purchaseDetailsList) async {
|
||||
for (final purchase in purchaseDetailsList) {
|
||||
core.connection.signalNotification(
|
||||
LogNotification('Purchase found: ${purchase.productID} - ${purchase.status}'),
|
||||
);
|
||||
if (purchase.status == PurchaseStatus.purchased || purchase.status == PurchaseStatus.restored) {
|
||||
IAPManager.instance.isPurchased.value = !kDebugMode;
|
||||
await _prefs.write(key: _purchaseStatusKey, value: IAPManager.instance.isPurchased.value.toString());
|
||||
@@ -376,7 +386,13 @@ class IAPService {
|
||||
_subscription?.cancel();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
_prefs.deleteAll();
|
||||
void reset(bool fullReset) {
|
||||
if (fullReset) {
|
||||
_prefs.deleteAll();
|
||||
} else {
|
||||
_prefs.delete(key: _purchaseStatusKey);
|
||||
_isInitialized = false;
|
||||
initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,8 +69,8 @@ class Settings {
|
||||
|
||||
Future<void> reset() async {
|
||||
await prefs.clear();
|
||||
IAPManager.instance.reset();
|
||||
core.actionHandler.init(null);
|
||||
IAPManager.instance.reset(true);
|
||||
init();
|
||||
}
|
||||
|
||||
void setTrainerApp(SupportedApp app) {
|
||||
|
||||
@@ -40,18 +40,25 @@ class _IAPStatusWidgetState extends State<IAPStatusWidget> {
|
||||
Widget build(BuildContext context) {
|
||||
final iapManager = IAPManager.instance;
|
||||
final isTrialExpired = iapManager.isTrialExpired;
|
||||
if (isTrialExpired) {
|
||||
_isSmall = false;
|
||||
}
|
||||
final trialDaysRemaining = iapManager.trialDaysRemaining;
|
||||
final commandsRemaining = iapManager.commandsRemainingToday;
|
||||
final dailyCommandCount = iapManager.dailyCommandCount;
|
||||
|
||||
return CardButton(
|
||||
return Button(
|
||||
onPressed: _isSmall
|
||||
? () {
|
||||
setState(() {
|
||||
_isSmall = false;
|
||||
});
|
||||
}
|
||||
: null,
|
||||
: _handlePurchase,
|
||||
style: ButtonStyle.card().withBackgroundColor(
|
||||
color: Theme.of(context).colorScheme.muted,
|
||||
hoverColor: Theme.of(context).colorScheme.primaryForeground,
|
||||
),
|
||||
child: AnimatedContainer(
|
||||
duration: Duration(milliseconds: 700),
|
||||
width: double.infinity,
|
||||
@@ -127,6 +134,8 @@ class _IAPStatusWidgetState extends State<IAPStatusWidget> {
|
||||
leadingAlignment: Alignment.centerLeft,
|
||||
leading: Icon(Icons.lock),
|
||||
title: Text(AppLocalizations.of(context).trialExpired(IAPManager.dailyCommandLimit)),
|
||||
trailing: _isSmall ? Icon(Icons.expand_more) : null,
|
||||
trailingAlignment: Alignment.centerRight,
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 6,
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart' show SelectionArea;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/i18n_extension.dart';
|
||||
import 'package:bike_control/widgets/ui/toast.dart';
|
||||
import 'package:flutter/material.dart' show SelectionArea;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
|
||||
import '../bluetooth/messages/notification.dart';
|
||||
|
||||
@@ -112,23 +110,6 @@ class _LogviewerState extends State<LogViewer> {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
if (!kIsWeb) ...[
|
||||
Text(context.i18n.logsAreAlsoAt).muted.small,
|
||||
CodeSnippet(
|
||||
code: SelectableText(File('${Directory.current.path}/app.logs').path),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.copy),
|
||||
variance: ButtonVariance.outline,
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: File('${Directory.current.path}/app.logs').path));
|
||||
buildToast(context, title: context.i18n.pathCopiedToClipboard);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bike_control/bluetooth/devices/zwift/zwift_clickv2.dart';
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/pages/markdown.dart';
|
||||
import 'package:bike_control/pages/navigation.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/i18n_extension.dart';
|
||||
import 'package:bike_control/widgets/title.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart' show showLicensePage;
|
||||
import 'package:in_app_review/in_app_review.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/zwift_clickv2.dart';
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/pages/markdown.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/i18n_extension.dart';
|
||||
import 'package:bike_control/widgets/title.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
List<Widget> buildMenuButtons(BuildContext context, VoidCallback? openLogs) {
|
||||
import '../utils/iap/iap_manager.dart';
|
||||
|
||||
List<Widget> buildMenuButtons(BuildContext context, BCPage currentPage, VoidCallback? openLogs) {
|
||||
return [
|
||||
Builder(
|
||||
builder: (context) {
|
||||
@@ -210,7 +213,7 @@ List<Widget> buildMenuButtons(BuildContext context, VoidCallback? openLogs) {
|
||||
},
|
||||
),
|
||||
Gap(4),
|
||||
BKMenuButton(openLogs: openLogs),
|
||||
BKMenuButton(openLogs: openLogs, currentPage: currentPage),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -231,7 +234,8 @@ ${core.connection.lastLogEntries.reversed.joinToString(separator: '\n', transfor
|
||||
|
||||
class BKMenuButton extends StatelessWidget {
|
||||
final VoidCallback? openLogs;
|
||||
const BKMenuButton({super.key, this.openLogs});
|
||||
final BCPage currentPage;
|
||||
const BKMenuButton({super.key, this.openLogs, required this.currentPage});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -267,6 +271,16 @@ class BKMenuButton extends StatelessWidget {
|
||||
),
|
||||
MenuDivider(),
|
||||
],
|
||||
if (currentPage == BCPage.logs) ...[
|
||||
MenuButton(
|
||||
child: Text('Reset IAP State'),
|
||||
onPressed: (c) async {
|
||||
IAPManager.instance.reset(false);
|
||||
core.settings.init();
|
||||
},
|
||||
),
|
||||
MenuDivider(),
|
||||
],
|
||||
if (openLogs != null)
|
||||
MenuButton(
|
||||
leading: Icon(Icons.article_outlined),
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/pages/button_simulator.dart';
|
||||
import 'package:bike_control/pages/markdown.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/i18n_extension.dart';
|
||||
import 'package:bike_control/utils/requirements/platform.dart';
|
||||
import 'package:bike_control/widgets/ignored_devices_dialog.dart';
|
||||
import 'package:bike_control/widgets/ui/connection_method.dart';
|
||||
import 'package:bike_control/widgets/ui/wifi_animation.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -78,6 +80,7 @@ class _ScanWidgetState extends State<ScanWidget> {
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(),
|
||||
if (!kIsWeb && (Platform.isMacOS || Platform.isWindows))
|
||||
ValueListenableBuilder(
|
||||
valueListenable: core.mediaKeyHandler.isMediaKeyDetectionEnabled,
|
||||
@@ -109,25 +112,57 @@ class _ScanWidgetState extends State<ScanWidget> {
|
||||
},
|
||||
),
|
||||
SizedBox(),
|
||||
if (core.connection.controllerDevices.isEmpty) ...[
|
||||
OutlineButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (c) => MarkdownPage(assetPath: 'TROUBLESHOOTING.md')),
|
||||
);
|
||||
},
|
||||
child: Text(context.i18n.showTroubleshootingGuide),
|
||||
),
|
||||
OutlineButton(
|
||||
onPressed: () {
|
||||
launchUrlString(
|
||||
'https://github.com/jonasbark/swiftcontrol/?tab=readme-ov-file#supported-devices',
|
||||
);
|
||||
},
|
||||
child: Text(context.i18n.showSupportedControllers),
|
||||
),
|
||||
],
|
||||
Column(
|
||||
spacing: 8,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
OutlineButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (c) => MarkdownPage(assetPath: 'TROUBLESHOOTING.md')),
|
||||
);
|
||||
},
|
||||
leading: Icon(Icons.help_outline),
|
||||
child: Text(context.i18n.showTroubleshootingGuide),
|
||||
),
|
||||
OutlineButton(
|
||||
onPressed: () {
|
||||
launchUrlString(
|
||||
'https://github.com/jonasbark/swiftcontrol/?tab=readme-ov-file#supported-devices',
|
||||
);
|
||||
},
|
||||
leading: Icon(Icons.gamepad_outlined),
|
||||
child: Text(context.i18n.showSupportedControllers),
|
||||
),
|
||||
if (core.settings.getIgnoredDevices().isNotEmpty)
|
||||
OutlineButton(
|
||||
leading: Icon(Icons.block_outlined),
|
||||
onPressed: () async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => IgnoredDevicesDialog(),
|
||||
);
|
||||
setState(() {});
|
||||
},
|
||||
child: Text(context.i18n.manageIgnoredDevices),
|
||||
),
|
||||
|
||||
if (core.connection.controllerDevices.isEmpty)
|
||||
PrimaryButton(
|
||||
leading: Icon(Icons.computer_outlined),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (c) => ButtonSimulator(),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(AppLocalizations.of(context).noControllerUseCompanionMode),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user