bugfix and animation

This commit is contained in:
Jonas Bark
2025-12-18 09:26:25 +01:00
parent b1e59d8f2a
commit dc1c900bd2
6 changed files with 112 additions and 163 deletions

View File

@@ -11,13 +11,11 @@ import 'package:bike_control/gen/l10n.dart';
import 'package:bike_control/main.dart';
import 'package:bike_control/utils/actions/android.dart';
import 'package:bike_control/utils/core.dart';
import 'package:bike_control/utils/keymap/keymap.dart';
import 'package:bike_control/utils/requirements/android.dart';
import 'package:dartx/dartx.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:gamepads/gamepads.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:universal_ble/universal_ble.dart';
import 'devices/base_device.dart';
@@ -312,20 +310,7 @@ class Connection {
await device.connect();
signalChange(device);
final newButtons = device.availableButtons.filter(
(button) => core.actionHandler.supportedApp?.keymap.getKeyPair(button) == null,
);
for (final button in newButtons) {
core.actionHandler.supportedApp?.keymap.addKeyPair(
KeyPair(
touchPosition: Offset.zero,
buttons: [button],
physicalKey: null,
logicalKey: null,
isLongPress: false,
),
);
}
core.actionHandler.supportedApp?.keymap.addNewButtons(device.availableButtons);
_streamSubscriptions[device] = actionSubscription;
} catch (e, backtrace) {

View File

@@ -32,6 +32,22 @@ class ButtonEditPage extends StatefulWidget {
class _ButtonEditPageState extends State<ButtonEditPage> {
late KeyPair _keyPair;
late final ScrollController _scrollController = ScrollController();
final double baseHeight = 46;
bool _bumped = false;
void _triggerBump() async {
setState(() {
_bumped = true;
});
await Future.delayed(const Duration(milliseconds: 150));
if (mounted) {
setState(() {
_bumped = false;
});
}
}
late StreamSubscription<BaseNotification> _actionSubscription;
@@ -52,6 +68,7 @@ class _ButtonEditPageState extends State<ButtonEditPage> {
setState(() {
_keyPair = keyPair;
});
_triggerBump();
}
}
});
@@ -83,7 +100,7 @@ class _ButtonEditPageState extends State<ButtonEditPage> {
padding: const EdgeInsets.only(right: 26.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 16,
spacing: 8,
children: [
SizedBox(height: 16),
Row(
@@ -93,7 +110,14 @@ class _ButtonEditPageState extends State<ButtonEditPage> {
spacing: 8,
children: [
Text('Editing').h3,
ButtonWidget(button: _keyPair.buttons.first),
AnimatedContainer(
duration: const Duration(milliseconds: 600),
curve: Curves.easeOut,
width: _keyPair.buttons.first.color != null ? baseHeight : null,
height: _keyPair.buttons.first.color != null ? baseHeight : null,
padding: EdgeInsets.all(_bumped ? 0 : 6.0),
child: ButtonWidget(button: _keyPair.buttons.first),
),
Expanded(child: SizedBox()),
IconButton(
icon: Icon(Icons.close),
@@ -115,140 +139,29 @@ class _ButtonEditPageState extends State<ButtonEditPage> {
),
if (core.logic.showObpActions) ...[
ColoredTitle(text: context.i18n.openBikeControlActions),
Builder(
builder: (context) => SelectableCard(
icon: _keyPair.inGameAction?.icon ?? Icons.link,
title: Text(
core.logic.obpConnectedApp == null
? 'Please connect to ${core.settings.getTrainerApp()?.name}, first.'
: context.i18n.appIdActions(core.logic.obpConnectedApp!.appId),
),
isActive: core.logic.obpConnectedApp != null && _keyPair.inGameAction != null,
onPressed: core.logic.obpConnectedApp == null
? null
: () {
showDropdown(
builder: (c) => DropdownMenu(
children: core.logic.obpConnectedApp!.supportedActions
.map(
(action) => MenuButton(
leading: action.icon != null ? Icon(action.icon) : null,
onPressed: (_) {
_keyPair.touchPosition = Offset.zero;
_keyPair.physicalKey = null;
_keyPair.logicalKey = null;
_keyPair.inGameAction = action;
_keyPair.inGameActionValue = null;
widget.onUpdate();
setState(() {});
},
child: Text(action.name),
),
)
.toList(),
),
context: context,
);
},
),
),
if (core.logic.obpConnectedApp == null)
Warning(
children: [
Text(
core.logic.obpConnectedApp == null
? 'Please connect to ${core.settings.getTrainerApp()?.name}, first.'
: context.i18n.appIdActions(core.logic.obpConnectedApp!.appId),
),
],
)
else
..._buildTrainerConnectionActions(core.logic.obpConnectedApp!.supportedActions),
],
if (core.settings.getMyWhooshLinkEnabled() && core.logic.showMyWhooshLink) ...[
SizedBox(height: 8),
ColoredTitle(text: context.i18n.myWhooshDirectConnectAction),
Builder(
builder: (context) => SelectableCard(
icon: _keyPair.inGameAction?.icon ?? Icons.link,
title: Text(context.i18n.myWhooshDirectConnectAction),
isActive:
_keyPair.inGameAction != null &&
core.whooshLink.supportedActions.contains(_keyPair.inGameAction),
value: [_keyPair.inGameAction.toString(), ?_keyPair.inGameActionValue?.toString()].join(' '),
onPressed: () {
showDropdown(
context: context,
builder: (c) => DropdownMenu(
children: core.whooshLink.supportedActions.map(
(ingame) {
return MenuButton(
subMenu: ingame.possibleValues
?.map(
(value) => MenuButton(
child: Text(value.toString()),
onPressed: (_) {
_keyPair.inGameAction = ingame;
_keyPair.inGameActionValue = value;
widget.onUpdate();
setState(() {});
},
),
)
.toList(),
leading: ingame.icon != null ? Icon(ingame.icon) : null,
child: Text(ingame.toString()),
onPressed: (_) {
_keyPair.inGameAction = ingame;
_keyPair.inGameActionValue = null;
widget.onUpdate();
setState(() {});
},
);
},
).toList(),
),
);
},
),
),
..._buildTrainerConnectionActions(core.whooshLink.supportedActions),
],
if (core.logic.isZwiftBleEnabled || core.logic.isZwiftMdnsEnabled) ...[
SizedBox(height: 8),
ColoredTitle(text: context.i18n.zwiftControllerAction),
Builder(
builder: (context) => SelectableCard(
icon: _keyPair.inGameAction?.icon ?? Icons.link,
title: Text(context.i18n.zwiftControllerAction),
isActive:
_keyPair.inGameAction != null &&
core.zwiftEmulator.supportedActions.contains(_keyPair.inGameAction),
value: [_keyPair.inGameAction.toString(), ?_keyPair.inGameActionValue?.toString()].join(' '),
onPressed: () {
showDropdown(
context: context,
builder: (c) => DropdownMenu(
children: core.zwiftEmulator.supportedActions.map(
(ingame) {
return MenuButton(
subMenu: ingame.possibleValues
?.map(
(value) => MenuButton(
child: Text(value.toString()),
onPressed: (_) {
_keyPair.inGameAction = ingame;
_keyPair.inGameActionValue = value;
widget.onUpdate();
setState(() {});
},
),
)
.toList(),
leading: ingame.icon != null ? Icon(ingame.icon) : null,
onPressed: (_) {
_keyPair.inGameAction = ingame;
_keyPair.inGameActionValue = null;
widget.onUpdate();
setState(() {});
},
child: Text(ingame.toString()),
);
},
).toList(),
),
);
},
),
),
..._buildTrainerConnectionActions(core.zwiftEmulator.supportedActions),
],
if (core.logic.showLocalRemoteOptions) ...[
@@ -510,6 +423,56 @@ class _ButtonEditPageState extends State<ButtonEditPage> {
),
);
}
List<Widget> _buildTrainerConnectionActions(List<InGameAction> supportedActions) {
return supportedActions.map((action) {
return Builder(
builder: (context) {
return SelectableCard(
icon: action.icon,
title: Text(action.title),
subtitle: (action.possibleValues != null && action == _keyPair.inGameAction)
? Text(_keyPair.inGameActionValue!.toString())
: null,
isActive: _keyPair.inGameAction == action && supportedActions.contains(_keyPair.inGameAction),
onPressed: () {
if (action.possibleValues?.isNotEmpty == true) {
showDropdown(
context: context,
builder: (c) => DropdownMenu(
children: action.possibleValues!.map(
(ingame) {
return MenuButton(
child: Text(ingame.toString()),
onPressed: (_) {
_keyPair.touchPosition = Offset.zero;
_keyPair.physicalKey = null;
_keyPair.logicalKey = null;
_keyPair.inGameAction = action;
_keyPair.inGameActionValue = ingame;
widget.onUpdate();
setState(() {});
},
);
},
).toList(),
),
);
} else {
_keyPair.touchPosition = Offset.zero;
_keyPair.physicalKey = null;
_keyPair.logicalKey = null;
_keyPair.inGameAction = action;
_keyPair.inGameActionValue = null;
widget.onUpdate();
setState(() {});
}
},
);
},
);
}).toList();
}
}
class SelectableCard extends StatelessWidget {

View File

@@ -97,10 +97,11 @@ class _CustomizeState extends State<CustomizePage> {
final customApp = CustomApp(profileName: profileName);
core.actionHandler.init(customApp);
await core.settings.setKeyMap(customApp);
setState(() {});
}
} else {
core.actionHandler.supportedApp = app;
core.actionHandler.init(app);
await core.settings.setKeyMap(app);
setState(() {});
}

View File

@@ -53,23 +53,8 @@ abstract class BaseActions {
debugPrint('Supported app: ${supportedApp?.name ?? "None"}');
if (supportedApp != null) {
final allButtons = core.connection.devices.map((e) => e.availableButtons).flatten().distinct();
final newButtons = allButtons.filter(
(button) => supportedApp.keymap.getKeyPair(button) == null,
);
for (final button in newButtons) {
supportedApp.keymap.addKeyPair(
KeyPair(
touchPosition: Offset.zero,
buttons: [button],
inGameAction: button.action,
physicalKey: null,
logicalKey: null,
isLongPress: false,
),
);
}
final allButtons = core.connection.devices.map((e) => e.availableButtons).flatten().distinct().toList();
supportedApp.keymap.addNewButtons(allButtons);
}
}

View File

@@ -20,8 +20,8 @@ enum InGameAction {
toggleUi('Toggle UI', icon: RadixIcons.iconSwitch),
navigateLeft('Navigate Left', icon: BootstrapIcons.signTurnLeft),
navigateRight('Navigate Right', icon: BootstrapIcons.signTurnRight),
increaseResistance('Increase Resistance'),
decreaseResistance('Decrease Resistance'),
increaseResistance('Increase Resistance', icon: LucideIcons.chartNoAxesColumnIncreasing),
decreaseResistance('Decrease Resistance', icon: LucideIcons.chartNoAxesColumnDecreasing),
// zwift
openActionBar('Open Action Bar', alternativeTitle: 'Up', icon: BootstrapIcons.menuApp),

View File

@@ -80,6 +80,21 @@ class Keymap {
return allButtons.firstWhere((b) => b.name == name);
}
}
void addNewButtons(List<ControllerButton> availableButtons) {
final newButtons = availableButtons.filter((button) => getKeyPair(button) == null);
for (final button in newButtons) {
addKeyPair(
KeyPair(
touchPosition: Offset.zero,
buttons: [button],
physicalKey: null,
logicalKey: null,
isLongPress: false,
),
);
}
}
}
class KeyPair {