add more instructions, clarify mail support

This commit is contained in:
Jonas Bark
2025-12-10 21:17:49 +01:00
parent d46b71b2d0
commit ce75fd0f34
18 changed files with 221 additions and 114 deletions

View File

@@ -1,4 +1,4 @@
### 4.0.1 (unreleased)
### 4.1.0 (unreleased)
**Features**:
- control your trainer manually without requiring a controller - just like a Companion app

View File

@@ -1,12 +1 @@
**Instructions for using the MyWhoosh "Link" connection method**
1) launch MyWhoosh on the device of your choice
2) launch MyWhoosh Link, check if the "Link" connection works
3) close MyWhoosh Link
4) open BikeControl, follow on screen instructions
Once you've confirmed the connection in BikeControl you won't have to repeat step 2 and 3 again in the future. This is just to make sure the connection works in general.
And here's a video with a few explanations:
[![BikeControl Instruction for iOS](https://img.youtube.com/vi/p8sgQhuufeI/0.jpg)](https://www.youtube.com/watch?v=p8sgQhuufeI)
[https://www.youtube.com/watch?v=p8sgQhuufeI](https://www.youtube.com/watch?v=p8sgQhuufeI)
Moved to [INSTRUCTIONS_MYWHOOSH_LINK.md](INSTRUCTIONS_MYWHOOSH_LINK.md)

View File

@@ -0,0 +1,28 @@
## Instructions for using the MyWhoosh "Link" connection method
*
1) launch MyWhoosh on the device of your choice
2) launch MyWhoosh Link, check if the "Link" connection works
3) close MyWhoosh Link
4) open BikeControl, follow on screen instructions
Once you've confirmed the connection in BikeControl you won't have to repeat step 2 and 3 again in the future. This is just to make sure the connection works in general.
And here's a video with a few explanations:
[![BikeControl Instruction for iOS](https://img.youtube.com/vi/p8sgQhuufeI/0.jpg)](https://www.youtube.com/watch?v=p8sgQhuufeI)
[https://www.youtube.com/watch?v=p8sgQhuufeI](https://www.youtube.com/watch?v=p8sgQhuufeI)
## MyWhoosh "Link" method never connects
*
The same network restrictions apply for BikeControl as it applies to MyWhoosh Link app. Please verify with the MyWhoosh Link app if connection is possible at all.
Here are some instructions that can help:
[https://mywhoosh.com/troubleshoot/](https://mywhoosh.com/troubleshoot/)
[https://www.facebook.com/groups/mywhoosh/posts/1323791068858873/](https://www.facebook.com/groups/mywhoosh/posts/1323791068858873/)
In essence:
- your two devices (phone, tablet) need to be on the same WiFi network
- on iOS you have to turn off "Private Wi-Fi Address" in the WiFi settings
- Limit IP Address Tracking may need to be disabled
- mesh networks may not work

View File

@@ -0,0 +1,13 @@
## Remote control is not working - nothing happens
*
- Try to unpair it from your phone / computer Bluetooth settings, then re-pair it.
- Try restarting the pairing process in BikeControl
- try restarting Bluetooth on your phone and on the device you want to control
- If your other device is an iOS device, go to Settings > Accessibility > Touch > AssistiveTouch > Pointer Devices > Devices and pair your device. Make sure AssistiveTouch is enabled.
- it is very important that both devices (e.g. iPhone and iPad) receive the "pairing dialog" after initial connection. If you miss it, unpair and try again. It may take a few seconds for the dialog to appear. Afterwards you may need to click on "Reconnect" in BikeControl / restart BikeControl.
## Remote control only clicks on a single coordinate on my iPad
*
iOS seems to be buggy here - try this in the iOS settings:
AssistiveTouch settings > Pointer Devices > Devices > Connected Devices > iPhone (or BikeControl iOS) > Button 1
switch the setting to None, then back to Single-Tap and it should work again

0
INSTRUCTIONS_ROUVY.md Normal file
View File

0
INSTRUCTIONS_ZWIFT.md Normal file
View File

View File

@@ -1,11 +1,14 @@
## Click / Ride device cannot be found
*
You may need to update the firmware in Zwift Companion app.
## Click / Ride device does not send any data
*
You may need to update the firmware in Zwift Companion app.
## My Click v2 disconnects after a minute
Check [this](https://github.com/OpenBikeControl/bikecontrol/issues/68) discussion.
*
Check [this](https://github.com/jonasbark/swiftcontrol/issues/68) discussion.
To make your Click V2 work best you should connect it in the Zwift app once each day.
If you don't do that BikeControl will need to reconnect every minute.
@@ -16,38 +19,15 @@ If you don't do that BikeControl will need to reconnect every minute.
4. Close the Zwift app again and connect again in BikeControl
## Android: Connection works, buttons work but nothing happens in MyWhoosh and similar
*
- especially for Redmi and other chinese Android devices please follow the instructions on [https://dontkillmyapp.com/](https://dontkillmyapp.com/):
- disable battery optimization for BikeControl
- enable auto start of BikeControl
- grant accessibility permission for BikeControl
- see [https://github.com/OpenBikeControl/bikecontrol/issues/38](https://github.com/OpenBikeControl/bikecontrol/issues/38) for more details
- see [https://github.com/jonasbark/swiftcontrol/issues/38](https://github.com/OpenBikeControl/bikecontrol/issues/38) for more details
## Remote control is not working - nothing happens
- Try to unpair it from your phone / computer Bluetooth settings, then re-pair it.
- Try restarting the pairing process in BikeControl
- try restarting Bluetooth on your phone and on the device you want to control
- If your other device is an iOS device, go to Settings > Accessibility > Touch > AssistiveTouch > Pointer Devices > Devices and pair your device. Make sure AssistiveTouch is enabled.
- it is very important that both devices (e.g. iPhone and iPad) receive the "pairing dialog" after initial connection. If you miss it, unpair and try again. It may take a few seconds for the dialog to appear. Afterwards you may need to click on "Reconnect" in BikeControl / restart BikeControl.
## Remote control only clicks on a single coordinate on my iPad
iOS seems to be buggy here - try this in the iOS settings:
AssistiveTouch settings > Pointer Devices > Devices > Connected Devices > iPhone (or BikeControl iOS) > Button 1
switch the setting to None, then back to Single-Tap and it should work again
## BikeControl crashes on Windows when searching for the device
## BikeControl crashes on Windows when searching for the device
*
You're probably running into [this](https://github.com/OpenBikeControl/bikecontrol/issues/70) issue. Disconnect your controller device (e.g. Zwift Play) from Windows Bluetooth settings.
## MyWhoosh "Link" method never connects
The same network restrictions apply for BikeControl as it applies to MyWhoosh Link app. Please verify with the MyWhoosh Link app if connection is possible at all.
Here are some instructions that can help:
[https://mywhoosh.com/troubleshoot/](https://mywhoosh.com/troubleshoot/)
[https://www.facebook.com/groups/mywhoosh/posts/1323791068858873/](https://www.facebook.com/groups/mywhoosh/posts/1323791068858873/)
[INSTRUCTIONS_IOS.md](INSTRUCTIONS_IOS.md)
In essence:
- your two devices (phone, tablet) need to be on the same WiFi network
- on iOS you have to turn off "Private Wi-Fi Address" in the WiFi settings
- Limit IP Address Tracking may need to be disabled
- mesh networks may not work

View File

@@ -210,6 +210,7 @@
"ignoredDevices": "Ignorierte Geräte",
"importAction": "Import",
"importProfile": "Profil importieren",
"instructions": "Anleitung",
"jsonData": "JSON-Daten",
"keyboardAccess": "Tastaturzugriff",
"latestVersion": "aktuell: {version}",
@@ -245,6 +246,7 @@
"logsHaveBeenCopiedToClipboard": "Die Protokolle wurden in die Zwischenablage kopiert.",
"longPress": "langes\nDrücken",
"longPressMode": "Modus „Langes Drücken“ (statt Wiederholen)",
"mailSupportExplanation": "Die individuelle Unterstützung per E-Mail ist für mich sehr aufwendig.\n\nBitte nutze daher Reddit, Facebook oder GitHub für Fragen und Probleme, damit die gesamte Community davon profitieren kann.",
"manageIgnoredDevices": "Ignorierte Geräte verwalten",
"manageProfile": "Profil verwalten",
"mediaKeyDetectionTooltip": "Aktiviere diese Option, damit BikeControl Bluetooth-Fernbedienungen erkennt. \nDazu muss BikeControl als Mediaplayer fungieren.",
@@ -260,6 +262,7 @@
"myWhooshDirectConnection": " z. B. mit MyWhoosh „Link”.",
"myWhooshLinkConnected": "MyWhoosh „Link“ verbunden",
"myWhooshLinkDescriptionLocal": "Verbinde dich direkt mit MyWhoosh über die „Link”-Methode. Unterstützte Aktionen sind unter anderem Schalten, Emotes und Richtungswechsel. Die MyWhoosh Link-Begleit-App darf dabei NICHT gleichzeitig laufen.",
"myWhooshLinkDescriptionRemote": "Du kannst dich über das Netzwerk mit MyWhoosh verbinden, indem du die „Link”-Verbindung nutzt. Die MyWhoosh Link-Begleit-App darf dabei NICHT gleichzeitig laufen.",
"nameChangeNotice": "SwiftControl heißt jetzt BikeControl! Es ist Teil des OpenBikeControl-Projekts, das sich für offene Standards bei intelligenten Fahrradtrainern einsetzt und erschwingliche Hardware-Controller entwickelt!",
"needHelpClickHelp": "Hilfe benötigt? Klicke auf",
"needHelpDontHesitate": "den Button oben und zögere nicht, uns zu kontaktieren.",

View File

@@ -454,5 +454,7 @@
"noConnectionMethodSelected": "No Connection Method selected",
"noControllerConnected": "None connected",
"notConnected": "Not connected",
"noTrainerSelected": "No Trainer selected"
"noTrainerSelected": "No Trainer selected",
"instructions": "Instructions",
"mailSupportExplanation": "Providing individual support via email is a lot of work for me.\n\nPlease consider using Reddit, Facebook or GitHub for questions and issues so that the whole community can benefit from it."
}

View File

@@ -210,6 +210,7 @@
"ignoredDevices": "Appareils ignorés",
"importAction": "Importer",
"importProfile": "Profil d'importation",
"instructions": "Mode d'emploi",
"jsonData": "Données JSON",
"keyboardAccess": "Accès clavier",
"latestVersion": "dernier: {version}",
@@ -245,6 +246,7 @@
"logsHaveBeenCopiedToClipboard": "Les journaux ont été copiés dans le presse-papiers.",
"longPress": "long\nappui",
"longPressMode": "Mode appui long (par rapport à la répétition)",
"mailSupportExplanation": "Répondre à tout le monde individuellement par e-mail, ça me prend beaucoup de temps.\n\nSi t'as des questions ou des problèmes, pense à utiliser Reddit, Facebook ou GitHub pour que tout le monde puisse en profiter.",
"manageIgnoredDevices": "Gérer les périphériques ignorés",
"manageProfile": "Gérer mon profil",
"mediaKeyDetectionTooltip": "Activez cette option pour permettre à BikeControl de détecter les télécommandes Bluetooth. Pour ce faire, BikeControl doit fonctionner comme un lecteur multimédia.",
@@ -256,10 +258,11 @@
"miuiWarningDescription": "Votre appareil fonctionne sous MIUI, qui est connu pour supprimer de manière agressive les services d'arrière-plan et les services d'accessibilité.",
"moreInformation": "Plus d'informations",
"mustChooseAllowOrDeny": "Vous devez choisir d'autoriser ou de refuser cette autorisation pour continuer.",
"myWhooshDirectConnectAction": "Action de connexion directe MyWhoosh",
"myWhooshDirectConnectAction": "Action «Link» de MyWhoosh",
"myWhooshDirectConnection": " par exemple en utilisant MyWhoosh «Link».",
"myWhooshLinkConnected": "MyWhoosh « Link » connecté",
"myWhooshLinkDescriptionLocal": "Connecte-toi directement à MyWhoosh avec la méthode « Link ». Tu peux faire des trucs comme changer de vitesse, utiliser des émoticônes, indiquer la direction à prendre, et plein d'autres choses. L'appli MyWhoosh Link ne doit PAS être ouverte en même temps.",
"myWhooshLinkDescriptionRemote": "Ça te permet de te connecter à MyWhoosh via le réseau, en utilisant la connexion « Link ». L'appli MyWhoosh Link ne doit PAS être ouverte en même temps.",
"nameChangeNotice": "SwiftControl devient BikeControl ! Ce logiciel fait partie du projet OpenBikeControl, qui promeut les standards ouverts pour les home trainers connectés et conçoit des contrôleurs matériels abordables !",
"needHelpClickHelp": "Besoin d'aide ? Cliquez sur le",
"needHelpDontHesitate": "bouton en haut et n'hésitez pas à nous contacter.",

View File

@@ -16,7 +16,7 @@ class MarkdownPage extends StatefulWidget {
}
class _ChangelogPageState extends State<MarkdownPage> {
Markdown? _markdown;
List<_Group>? _groups;
String? _error;
@override
@@ -28,9 +28,7 @@ class _ChangelogPageState extends State<MarkdownPage> {
Future<void> _loadChangelog() async {
try {
final md = await rootBundle.loadString(widget.assetPath);
setState(() {
_markdown = Markdown.fromString(md);
});
_parseMarkdown(md);
// load latest version
final response = await http.get(
@@ -39,9 +37,7 @@ class _ChangelogPageState extends State<MarkdownPage> {
if (response.statusCode == 200) {
final latestMd = response.body;
if (latestMd != md) {
setState(() {
_markdown = Markdown.fromString(md);
});
_parseMarkdown(md);
}
}
} catch (e) {
@@ -59,55 +55,37 @@ class _ChangelogPageState extends State<MarkdownPage> {
leading: [
BackButton(),
],
title: Text(widget.assetPath.replaceAll('.md', '').toLowerCase().capitalize()),
title: Text(
widget.assetPath
.replaceAll('.md', '')
.split('_')
.joinToString(separator: ' ', transform: (s) => s.toLowerCase().capitalize()),
),
),
],
child: _error != null
? Center(child: Text(_error!))
: _markdown == null
: _groups == null
? Center(child: CircularProgressIndicator())
: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Accordion(
items: _markdown!.blocks.fold(<Widget>[], (acc, block) {
if (block is MD$Heading) {
acc.add(
AccordionItem(
trigger: AccordionTrigger(child: ColoredTitle(text: block.text)),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [],
items: _groups!
.map(
(group) => AccordionItem(
trigger: AccordionTrigger(child: ColoredTitle(text: group.title)),
content: MarkdownWidget(
markdown: group.markdown,
theme: MarkdownThemeData(
textStyle: TextStyle(),
onLinkTap: (title, url) {
launchUrlString(url);
},
),
),
),
);
} else {
((acc.last as AccordionItem).content as Column).children.add(
switch (block.type) {
_ when block is MD$Paragraph => Text(block.text).small,
_ when block is MD$List => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (var item in block.items) ...[
if (item.children.isEmpty)
fromString(item.text).li
else
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
fromString(item.text),
for (var line in item.children) fromString(line.text).li,
],
).li,
],
],
),
_ when block is MD$Spacer => SizedBox(height: 16),
_ => SizedBox.shrink(),
},
);
}
return acc;
}),
)
.toList(),
),
),
);
@@ -125,4 +103,29 @@ class _ChangelogPageState extends State<MarkdownPage> {
),
);
}
void _parseMarkdown(String md) {
setState(() {
_groups = md
.split('## ')
.map((section) {
final lines = section.split('\n');
final title = lines.first.replaceFirst('# ', '').trim();
final content = lines.skip(1).join('\n').trim();
return _Group(
title: title,
markdown: Markdown.fromString('## $content'),
);
})
.where((group) => group.title.isNotEmpty)
.toList();
});
}
}
class _Group {
final String title;
final Markdown markdown;
_Group({required this.title, required this.markdown});
}

View File

@@ -24,7 +24,7 @@ class _MywhooshLinkTileState extends State<MyWhooshLinkTile> {
isEnabled: core.settings.getMyWhooshLinkEnabled(),
type: ConnectionMethodType.network,
title: context.i18n.connectUsingMyWhooshLink,
instructionLink: 'https://github.com/jonasbark/swiftcontrol/blob/main/INSTRUCTIONS_IOS.md',
instructionLink: 'INSTRUCTIONS_MYWHOOSH_LINK.md',
description: isConnected
? context.i18n.myWhooshLinkConnected
: isStarted

View File

@@ -34,6 +34,7 @@ class _ZwiftTileState extends State<ZwiftMdnsTile> {
: isConnected
? context.i18n.connected
: context.i18n.waitingForConnectionKickrBike(core.settings.getTrainerApp()?.name ?? ''),
instructionLink: 'INSTRUCTIONS_ZWIFT.md',
isStarted: isStarted,
isConnected: isConnected,
onChange: (start) {

View File

@@ -28,6 +28,7 @@ class _ZwiftTileState extends State<ZwiftTile> {
return ConnectionMethod(
isEnabled: core.settings.getZwiftBleEmulatorEnabled(),
type: ConnectionMethodType.bluetooth,
instructionLink: 'INSTRUCTIONS_ZWIFT.md',
isStarted: isStarted,
isConnected: isConnected,
onChange: (value) {

View File

@@ -7,6 +7,7 @@ import 'package:in_app_review/in_app_review.dart';
import 'package:intl/intl.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:swift_control/bluetooth/devices/zwift/zwift_clickv2.dart';
import 'package:swift_control/gen/l10n.dart';
import 'package:swift_control/pages/markdown.dart';
import 'package:swift_control/utils/core.dart';
import 'package:swift_control/utils/i18n_extension.dart';
@@ -98,35 +99,111 @@ List<Widget> buildMenuButtons(BuildContext context, VoidCallback? openLogs) {
);
},
),
MenuDivider(),
MenuLabel(child: Text(context.i18n.getSupport)),
MenuButton(
leading: Icon(Icons.bug_report_outlined),
child: Text(context.i18n.provideFeedback),
leading: Icon(Icons.reddit_outlined),
onPressed: (c) {
launchUrlString('https://www.reddit.com/r/BikeControl/');
},
child: Text('Reddit'),
),
MenuButton(
leading: Icon(Icons.facebook_outlined),
onPressed: (c) {
launchUrlString('https://www.facebook.com/groups/1892836898778912');
},
child: Text('Facebook'),
),
MenuButton(
leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 3.0),
child: Text('G'),
),
onPressed: (c) {
launchUrlString('https://github.com/jonasbark/swiftcontrol/issues');
},
child: Text('GitHub'),
),
MenuDivider(),
if (!kIsWeb)
if (!kIsWeb) ...[
MenuButton(
leading: Icon(Icons.email_outlined),
child: Text(context.i18n.getSupport),
child: Text('Mail'),
onPressed: (c) {
final isFromStore = (Platform.isAndroid ? isFromPlayStore == true : Platform.isIOS);
final suffix = isFromStore ? '' : '-sw';
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Mail Support'),
content: Container(
constraints: BoxConstraints(maxWidth: 400),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 16,
children: [
Text(
AppLocalizations.of(context).mailSupportExplanation,
),
...[
OutlineButton(
leading: Icon(Icons.reddit_outlined),
onPressed: () {
Navigator.pop(context);
launchUrlString('https://www.reddit.com/r/BikeControl/');
},
child: const Text('Reddit'),
),
OutlineButton(
leading: Icon(Icons.facebook_outlined),
onPressed: () {
Navigator.pop(context);
launchUrlString('https://www.facebook.com/groups/1892836898778912');
},
child: const Text('Facebook'),
),
OutlineButton(
leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 3.0),
child: Text('G'),
),
onPressed: () {
Navigator.pop(context);
launchUrlString('https://github.com/jonasbark/swiftcontrol/issues');
},
child: const Text('GitHub'),
),
SecondaryButton(
leading: Icon(Icons.mail_outlined),
onPressed: () {
Navigator.pop(context);
String email = Uri.encodeComponent('jonas$suffix@bikecontrol.app');
String subject = Uri.encodeComponent(
context.i18n.helpRequested(packageInfoValue?.version ?? ''),
final isFromStore = (Platform.isAndroid
? isFromPlayStore == true
: Platform.isIOS);
final suffix = isFromStore ? '' : '-sw';
String email = Uri.encodeComponent('jonas$suffix@bikecontrol.app');
String subject = Uri.encodeComponent(
context.i18n.helpRequested(packageInfoValue?.version ?? ''),
);
String body = Uri.encodeComponent("""
${debugText()}""");
Uri mail = Uri.parse("mailto:$email?subject=$subject&body=$body");
launchUrl(mail);
},
child: const Text('Mail'),
),
],
],
),
),
);
},
);
String body = Uri.encodeComponent("""
${debugText()}
${context.i18n.attachLogFile(File('${Directory.current.path}/app.logs').path)}""");
Uri mail = Uri.parse("mailto:$email?subject=$subject&body=$body");
launchUrl(mail);
},
),
],
],
),
);

View File

@@ -30,6 +30,7 @@ class _PairWidgetState extends State<RemotePairingWidget> {
isStarted: isStarted,
showTroubleshooting: true,
type: ConnectionMethodType.bluetooth,
instructionLink: 'INSTRUCTIONS_REMOTE_CONTROL.md',
title: context.i18n.enablePairingProcess,
description: context.i18n.pairingDescription,
isConnected: isConnected,

View File

@@ -1,6 +1,7 @@
import 'package:dartx/dartx.dart';
import 'package:flutter/foundation.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:swift_control/gen/l10n.dart';
import 'package:swift_control/pages/button_edit.dart';
import 'package:swift_control/pages/markdown.dart';
import 'package:swift_control/utils/i18n_extension.dart';
@@ -8,7 +9,6 @@ import 'package:swift_control/utils/requirements/platform.dart';
import 'package:swift_control/widgets/ui/beta_pill.dart';
import 'package:swift_control/widgets/ui/small_progress_indicator.dart';
import 'package:swift_control/widgets/ui/toast.dart';
import 'package:url_launcher/url_launcher_string.dart';
enum ConnectionMethodType {
bluetooth,
@@ -154,13 +154,16 @@ class _ConnectionMethodState extends State<ConnectionMethod> with WidgetsBinding
style: widget.isEnabled && Theme.of(context).brightness == Brightness.light
? ButtonStyle.outline().withBorder(border: Border.all(color: Colors.gray.shade500))
: ButtonStyle.outline(),
leading: Icon(Icons.play_circle_outline_outlined),
leading: Icon(Icons.help_outline),
onPressed: () {
launchUrlString(widget.instructionLink!);
Navigator.push(
context,
MaterialPageRoute(builder: (c) => MarkdownPage(assetPath: widget.instructionLink!)),
);
},
child: Text(context.i18n.videoInstructions),
child: Text(AppLocalizations.of(context).instructions),
),
if (widget.showTroubleshooting)
if (widget.showTroubleshooting && widget.instructionLink == null)
Button(
style: widget.isEnabled && Theme.of(context).brightness == Brightness.light
? ButtonStyle.outline().withBorder(border: Border.all(color: Colors.gray.shade500))

View File

@@ -1,7 +1,7 @@
name: swift_control
description: "BikeControl - Control your virtual riding"
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 4.0.0+48
version: 4.1.0+49
environment:
sdk: ^3.9.0
@@ -83,7 +83,10 @@ flutter:
assets:
- CHANGELOG.md
- TROUBLESHOOTING.md
- INSTRUCTIONS_IOS.md
- INSTRUCTIONS_MYWHOOSH_LINK.md
- INSTRUCTIONS_REMOTE_CONTROL.md
- INSTRUCTIONS_ROUVY.md
- INSTRUCTIONS_ZWIFT.md
- shorebird.yaml
- icon.png