mirror of
https://github.com/jonasbark/swiftcontrol.git
synced 2026-02-18 00:17:40 +01:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41a3a8f14d | ||
|
|
a883abcd1c | ||
|
|
ab37de8f40 | ||
|
|
ac0e15eaa7 | ||
|
|
a6a7e7f0c2 | ||
|
|
3cacdf9a3a | ||
|
|
3ebbda3690 | ||
|
|
74abb13acf |
@@ -1,3 +1,12 @@
|
||||
### 4.4.0 (16-01-2026)
|
||||
|
||||
**Features**:
|
||||
- Support for Thinkrider VS200
|
||||
|
||||
**Fixes**:
|
||||
- Android: Local connection method allows passing keyboard events to the trainer app
|
||||
- macOS: Compatibility with macOS Tahoe
|
||||
|
||||
### 4.3.0 (07-01-2026)
|
||||
|
||||
**Features**:
|
||||
|
||||
@@ -16,6 +16,7 @@ This is a network/local-discovery problem. BikeControl needs the same kind of lo
|
||||
|
||||
Checklist:
|
||||
- Use the MyWhoosh Link app to confirm if "Link" works in general
|
||||
- Use MyWhoosh Link app and connect, then close it, then open up BikeControl - this is key for some users
|
||||
- Both devices are on the **same Wi‑Fi SSID**
|
||||
- Avoid “Guest” networks
|
||||
- Avoid “extenders/mesh guest mode” and networks with device isolation
|
||||
|
||||
@@ -59,7 +59,15 @@ abstract class BluetoothDevice extends BaseDevice {
|
||||
ThinkRiderVs200Constants.SERVICE_UUID,
|
||||
];
|
||||
|
||||
static final List<String> _ignoredNames = ['ASSIOMA', 'QUARQ', 'POWERCRANK'];
|
||||
|
||||
static BluetoothDevice? fromScanResult(BleDevice scanResult) {
|
||||
// skip devices with ignored names
|
||||
if (scanResult.name != null &&
|
||||
_ignoredNames.any((ignoredName) => scanResult.name!.toUpperCase().startsWith(ignoredName))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use the name first as the "System Devices" and Web (android sometimes Windows) don't have manufacturer data
|
||||
BluetoothDevice? device;
|
||||
if (kIsWeb) {
|
||||
@@ -108,8 +116,9 @@ abstract class BluetoothDevice extends BaseDevice {
|
||||
OpenBikeControlDevice(scanResult),
|
||||
_ when scanResult.services.contains(WahooKickrHeadwindConstants.SERVICE_UUID.toLowerCase()) =>
|
||||
WahooKickrHeadwind(scanResult),
|
||||
_ when scanResult.services.contains(ThinkRiderVs200Constants.SERVICE_UUID.toLowerCase()) =>
|
||||
ThinkRiderVs200(scanResult),
|
||||
_ when scanResult.services.contains(ThinkRiderVs200Constants.SERVICE_UUID.toLowerCase()) => ThinkRiderVs200(
|
||||
scanResult,
|
||||
),
|
||||
// otherwise the service UUIDs will be used
|
||||
_ => null,
|
||||
};
|
||||
|
||||
@@ -80,6 +80,7 @@ class WahooKickrHeadwind extends BluetoothDevice {
|
||||
service,
|
||||
characteristic,
|
||||
manualModeData,
|
||||
withoutResponse: true,
|
||||
);
|
||||
_currentMode = HeadwindMode.manual;
|
||||
}
|
||||
@@ -93,6 +94,7 @@ class WahooKickrHeadwind extends BluetoothDevice {
|
||||
service,
|
||||
characteristic,
|
||||
data,
|
||||
withoutResponse: true,
|
||||
);
|
||||
_currentSpeed = speedPercent;
|
||||
}
|
||||
@@ -109,6 +111,7 @@ class WahooKickrHeadwind extends BluetoothDevice {
|
||||
service,
|
||||
characteristic,
|
||||
data,
|
||||
withoutResponse: true,
|
||||
);
|
||||
_currentMode = HeadwindMode.heartRate;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ Future<void> _persistCrash({
|
||||
..writeln();
|
||||
|
||||
final directory = await _getLogDirectory();
|
||||
final file = File('${directory.path}/app.logs');
|
||||
final file = File('${directory.path}/app.log');
|
||||
final fileLength = await file.length();
|
||||
if (fileLength > 5 * 1024 * 1024) {
|
||||
// If log file exceeds 5MB, truncate it
|
||||
|
||||
@@ -254,7 +254,7 @@ class _TrainerPageState extends State<TrainerPage> with WidgetsBindingObserver {
|
||||
|
||||
SizedBox(height: 4),
|
||||
Flex(
|
||||
direction: widget.isMobile ? Axis.vertical : Axis.horizontal,
|
||||
direction: widget.isMobile || MediaQuery.sizeOf(context).width < 750 ? Axis.vertical : Axis.horizontal,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 8,
|
||||
|
||||
@@ -177,10 +177,11 @@ class RevenueCatService {
|
||||
core.connection.signalNotification(LogNotification('Apple receipt validated for version: $purchasedVersion'));
|
||||
if (purchasedVersion != null && purchasedVersion.contains(".")) {
|
||||
final parsedVersion = Version.parse(purchasedVersion);
|
||||
isPurchasedNotifier.value = parsedVersion < Version(4, 2, 0);
|
||||
isPurchasedNotifier.value = parsedVersion < Version(4, 2, 0) || parsedVersion >= Version(4, 4, 0);
|
||||
} else {
|
||||
final purchasedVersionAsInt = int.tryParse(purchasedVersion.toString()) ?? 1337;
|
||||
isPurchasedNotifier.value = purchasedVersionAsInt < (Platform.isMacOS ? 61 : 58);
|
||||
isPurchasedNotifier.value =
|
||||
purchasedVersionAsInt < (Platform.isMacOS ? 61 : 58) || purchasedVersionAsInt >= 77;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -192,22 +192,19 @@ class NotificationRequirement extends PlatformRequirement {
|
||||
} else {
|
||||
status = true;
|
||||
}
|
||||
if (status) {
|
||||
await setup();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static Future<void> setup() async {
|
||||
const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings(
|
||||
'@mipmap/ic_launcher',
|
||||
);
|
||||
|
||||
await core.flutterLocalNotificationsPlugin.initialize(
|
||||
InitializationSettings(
|
||||
android: initializationSettingsAndroid,
|
||||
android: AndroidInitializationSettings(
|
||||
'@mipmap/ic_launcher',
|
||||
),
|
||||
iOS: DarwinInitializationSettings(
|
||||
requestAlertPermission: false,
|
||||
requestBadgePermission: false,
|
||||
requestSoundPermission: false,
|
||||
),
|
||||
macOS: DarwinInitializationSettings(
|
||||
requestAlertPermission: false,
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:bike_control/bluetooth/devices/gyroscope/gyroscope_steering.dart
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/iap/iap_manager.dart';
|
||||
import 'package:bike_control/utils/keymap/apps/supported_app.dart';
|
||||
import 'package:bike_control/utils/requirements/android.dart';
|
||||
import 'package:bike_control/utils/requirements/multi.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -25,6 +26,11 @@ class Settings {
|
||||
Future<String?> init({bool retried = false}) async {
|
||||
try {
|
||||
prefs = await SharedPreferences.getInstance();
|
||||
try {
|
||||
await NotificationRequirement.setup();
|
||||
} catch (error, stack) {
|
||||
recordError(error, stack, context: 'Notification setup');
|
||||
}
|
||||
initializeActions(getLastTarget()?.connectionType ?? ConnectionType.unknown);
|
||||
|
||||
if (getShowOnboarding() && getTrainerApp() != null) {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
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:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart' show SelectionArea;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
@@ -114,6 +116,17 @@ class _LogviewerState extends State<LogViewer> {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
if (!kIsWeb && (Platform.isMacOS || Platform.isWindows || Platform.isLinux))
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Text('Logs file: '),
|
||||
SelectableText('${Directory.current.path}/app.log').inlineCode,
|
||||
],
|
||||
).small,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -176,6 +176,7 @@ class _ConnectionMethodState extends State<ConnectionMethod> with WidgetsBinding
|
||||
if (widget.supportedActions != null)
|
||||
Button.outline(
|
||||
leading: Container(
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: bike_control
|
||||
description: "BikeControl - Control your virtual riding"
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
version: 4.3.1+76
|
||||
version: 4.4.0+77
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.0
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:bike_control/bluetooth/devices/bluetooth_device.dart';
|
||||
import 'package:bike_control/bluetooth/devices/cycplus/cycplus_bc2.dart';
|
||||
import 'package:bike_control/bluetooth/devices/elite/elite_square.dart';
|
||||
import 'package:bike_control/bluetooth/devices/elite/elite_sterzo.dart';
|
||||
import 'package:bike_control/bluetooth/devices/shimano/shimano_di2.dart';
|
||||
import 'package:bike_control/bluetooth/devices/sram/sram_axs.dart';
|
||||
import 'package:bike_control/bluetooth/devices/wahoo/wahoo_kickr_bike_shift.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/constants.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/zwift_click.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/zwift_clickv2.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/zwift_play.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/zwift_ride.dart';
|
||||
import 'package:bike_control/utils/actions/base_actions.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
void main() {
|
||||
core.actionHandler = StubActions();
|
||||
|
||||
group('Detect Zwift devices', () {
|
||||
test('Detect Zwift Play', () {
|
||||
final device = _createBleDevice(
|
||||
@@ -108,6 +113,17 @@ void main() {
|
||||
expect(BluetoothDevice.fromScanResult(device), isInstanceOf<ShimanoDi2>());
|
||||
});
|
||||
});
|
||||
|
||||
group('Skip powermeters', () {
|
||||
test('Skip Favero Assioma', () {
|
||||
final device = _createBleDevice(name: 'Assioma 133', services: [SramAxsConstants.SERVICE_UUID]);
|
||||
expect(BluetoothDevice.fromScanResult(device), isNull);
|
||||
});
|
||||
test('Skip QUARQ', () {
|
||||
final device = _createBleDevice(name: 'QUARQ 133', services: [SramAxsConstants.SERVICE_UUID]);
|
||||
expect(BluetoothDevice.fromScanResult(device), isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
BleDevice _createBleDevice({
|
||||
|
||||
Reference in New Issue
Block a user