diff --git a/lib/pages/touch_area.dart b/lib/pages/touch_area.dart index 602b69c..e2dd045 100644 --- a/lib/pages/touch_area.dart +++ b/lib/pages/touch_area.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path_provider/path_provider.dart'; import 'package:swift_control/main.dart'; +import 'package:swift_control/utils/keymap/apps/my_whoosh.dart'; import 'package:swift_control/widgets/button_widget.dart'; import 'package:swift_control/widgets/keymap_explanation.dart'; import 'package:swift_control/widgets/testbed.dart'; @@ -29,12 +30,12 @@ class TouchAreaSetupPage extends StatefulWidget { } class _TouchAreaSetupPageState extends State { - File? _backgroundImage; + Uint8List? _backgroundImage; final TransformationController _transformationController = TransformationController(); late Rect _imageRect; - bool _showAll = false; + bool _showFaded = true; Future _pickScreenshot() async { final picker = ImagePicker(); @@ -44,7 +45,7 @@ class _TouchAreaSetupPageState extends State { final Directory tempDir = await getTemporaryDirectory(); final tempImage = File('${tempDir.path}/${actionHandler.supportedApp?.name ?? 'temp'}_screenshot.png'); await image.copy(tempImage.path); - _backgroundImage = tempImage; + _backgroundImage = tempImage.readAsBytesSync(); await _calculateBounds(); } } @@ -53,7 +54,7 @@ class _TouchAreaSetupPageState extends State { if (_backgroundImage == null) return; // need to decode image to get its size so we can have a percentage mapping - final decodedImage = await decodeImageFromList(_backgroundImage!.readAsBytesSync()); + final decodedImage = await decodeImageFromList(_backgroundImage!); // calculate image rectangle in the current screen, given it's boxfit contain final screenSize = MediaQuery.sizeOf(context); final imageAspectRatio = decodedImage.width / decodedImage.height; @@ -114,7 +115,7 @@ class _TouchAreaSetupPageState extends State { getTemporaryDirectory().then((tempDir) async { final tempImage = File('${tempDir.path}/${actionHandler.supportedApp?.name ?? 'temp'}_screenshot.png'); if (tempImage.existsSync()) { - _backgroundImage = tempImage; + _backgroundImage = tempImage.readAsBytesSync(); setState(() {}); // wait a bit until device rotation is done @@ -197,41 +198,50 @@ class _TouchAreaSetupPageState extends State { top: position.dy, child: Tooltip( message: 'Drag to reposition', - child: Draggable( - dragAnchorStrategy: (widget, context, position) { - final scale = _transformationController.value.getMaxScaleOnAxis(); - final RenderBox renderObject = context.findRenderObject() as RenderBox; - return renderObject.globalToLocal(position).scale(scale, scale); - }, - feedback: Material( - color: Colors.transparent, + child: AnimatedOpacity( + opacity: _showFaded && widget.keyPair != keyPair ? 0.2 : 1.0, + duration: Duration(milliseconds: 300), + child: Draggable( + dragAnchorStrategy: (widget, context, position) { + final scale = _transformationController.value.getMaxScaleOnAxis(); + final RenderBox renderObject = context.findRenderObject() as RenderBox; + return renderObject.globalToLocal(position).scale(scale, scale); + }, + feedback: Material( + color: Colors.transparent, + child: icon, + ), + childWhenDragging: const SizedBox.shrink(), + onDragStarted: () { + // Capture the starting position to calculate drag distance later + dragStartPosition = position; + if (keyPair != widget.keyPair && _showFaded) { + setState(() { + _showFaded = false; + }); + } + }, + onDragEnd: (details) { + // Calculate drag distance to prevent accidental repositioning from clicks + // while allowing legitimate drags even with low velocity (e.g., when overlapping buttons) + final dragDistance = dragStartPosition != null + ? (details.offset - dragStartPosition!).distance + : double.infinity; + + // Only update position if dragged more than 5 pixels (prevents accidental clicks) + if (dragDistance > 5) { + final matrix = Matrix4.inverted(_transformationController.value); + final height = 0; + final sceneY = details.offset.dy - height; + final viewportPoint = MatrixUtils.transformPoint( + matrix, + Offset(details.offset.dx, sceneY) + Offset(iconSize / 2, differenceInHeight + iconSize / 2), + ); + setState(() => onPositionChanged(viewportPoint)); + } + }, child: icon, ), - childWhenDragging: const SizedBox.shrink(), - onDragStarted: () { - // Capture the starting position to calculate drag distance later - dragStartPosition = position; - }, - onDragEnd: (details) { - // Calculate drag distance to prevent accidental repositioning from clicks - // while allowing legitimate drags even with low velocity (e.g., when overlapping buttons) - final dragDistance = dragStartPosition != null - ? (details.offset - dragStartPosition!).distance - : double.infinity; - - // Only update position if dragged more than 5 pixels (prevents accidental clicks) - if (dragDistance > 5) { - final matrix = Matrix4.inverted(_transformationController.value); - final height = 0; - final sceneY = details.offset.dy - height; - final viewportPoint = MatrixUtils.transformPoint( - matrix, - Offset(details.offset.dx, sceneY) + Offset(iconSize / 2, differenceInHeight + iconSize / 2), - ); - setState(() => onPositionChanged(viewportPoint)); - } - }, - child: icon, ), ), ); @@ -245,12 +255,11 @@ class _TouchAreaSetupPageState extends State { if (_backgroundImage == null && constraints.biggest != _imageRect.size) { _imageRect = Rect.fromLTWH(0, 0, constraints.maxWidth, constraints.maxHeight); } - final keyPairsToShow = _showAll - ? actionHandler.supportedApp?.keymap.keyPairs - .where((kp) => kp.touchPosition != Offset.zero && !kp.isSpecialKey) - .toList() ?? - [] - : [widget.keyPair]; + final keyPairsToShow = + actionHandler.supportedApp?.keymap.keyPairs + .where((kp) => kp.touchPosition != Offset.zero && !kp.isSpecialKey) + .toList() ?? + []; return InteractiveViewer( transformationController: _transformationController, child: Stack( @@ -259,7 +268,7 @@ class _TouchAreaSetupPageState extends State { Positioned.fill( child: Opacity( opacity: 0.5, - child: Image.file( + child: Image.memory( _backgroundImage!, fit: BoxFit.contain, ), @@ -338,23 +347,9 @@ class _TouchAreaSetupPageState extends State { PopupMenuButton( itemBuilder: (c) => [ PopupMenuItem( - child: Row( - children: [ - Checkbox( - value: _showAll, - onChanged: (_) { - setState(() { - _showAll = !_showAll; - }); - }, - ), - Text('Show all touch areas'), - ], - ), + child: Text('Choose another screenshot'), onTap: () { - setState(() { - _showAll = !_showAll; - }); + _pickScreenshot(); }, ), PopupMenuItem( @@ -400,7 +395,9 @@ class KeypairExplanation extends StatelessWidget { ) else Icon(keyPair.icon), - if (keyPair.inGameAction != null) + if (keyPair.inGameAction != null && + ((settings.getTrainerApp() is MyWhoosh && settings.getMyWhooshLinkEnabled()) || + (settings.getTrainerApp()?.supportsZwiftEmulation == true && settings.getZwiftEmulatorEnabled()))) _KeyWidget( label: [ keyPair.inGameAction.toString().split('.').last, diff --git a/lib/utils/keymap/keymap.dart b/lib/utils/keymap/keymap.dart index e93e86c..4d6fe69 100644 --- a/lib/utils/keymap/keymap.dart +++ b/lib/utils/keymap/keymap.dart @@ -5,6 +5,7 @@ import 'package:dartx/dartx.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:swift_control/main.dart'; +import 'package:swift_control/utils/keymap/apps/my_whoosh.dart'; import 'package:swift_control/utils/keymap/buttons.dart'; import '../actions/base_actions.dart'; @@ -40,7 +41,14 @@ class Keymap { } void reset() { - keyPairs = []; + for (final keyPair in keyPairs) { + keyPair.physicalKey = null; + keyPair.logicalKey = null; + keyPair.touchPosition = Offset.zero; + keyPair.isLongPress = false; + keyPair.inGameAction = null; + keyPair.inGameActionValue = null; + } _updateStream.add(null); } @@ -90,7 +98,11 @@ class KeyPair { PhysicalKeyboardKey.audioVolumeUp || PhysicalKeyboardKey.audioVolumeDown => Icons.music_note_outlined, _ when physicalKey != null && actionHandler.supportedModes.contains(SupportedMode.keyboard) => Icons.keyboard, - _ when inGameAction != null => Icons.link, + _ + when inGameAction != null && + ((settings.getTrainerApp() is MyWhoosh && settings.getMyWhooshLinkEnabled()) || + (settings.getTrainerApp()?.supportsZwiftEmulation == true && settings.getZwiftEmulatorEnabled())) => + Icons.link, _ => Icons.touch_app, }; }