mirror of
https://github.com/jonasbark/swiftcontrol.git
synced 2026-02-18 00:17:40 +01:00
Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
586b17c2d2 | ||
|
|
4909a1a47f | ||
|
|
99e603413c | ||
|
|
875f5cb656 | ||
|
|
7ad65ba5dc | ||
|
|
7e969b1a94 | ||
|
|
5689980c87 | ||
|
|
b14a6451ed | ||
|
|
7a52828bd1 | ||
|
|
6926f5d3d5 | ||
|
|
cb6283364a | ||
|
|
1e220799be | ||
|
|
f0ad53e9d4 | ||
|
|
b0bf0bd802 | ||
|
|
e3fc35211f | ||
|
|
8c77bcea2a | ||
|
|
1693605305 | ||
|
|
93ad9d3a30 | ||
|
|
15bc4ab2af | ||
|
|
6c0942acad | ||
|
|
351e702238 | ||
|
|
8119d69c1a | ||
|
|
13d11dd927 | ||
|
|
f4c47071fb | ||
|
|
7043d16108 | ||
|
|
eb587c4341 | ||
|
|
80bd4725d1 | ||
|
|
25ff46d527 | ||
|
|
7acd86fc94 | ||
|
|
26047da35c | ||
|
|
d3ab4f8804 | ||
|
|
29940d45ba | ||
|
|
c6296b009a | ||
|
|
130a638e2b | ||
|
|
12a5d9e52b | ||
|
|
2e69cdb593 | ||
|
|
6d6427f8cd | ||
|
|
2d73d8577e | ||
|
|
19e72af527 | ||
|
|
1692c584f4 | ||
|
|
ab8e1b6afe | ||
|
|
9a7c4bb13a | ||
|
|
b97298099c | ||
|
|
a7a91e00a2 | ||
|
|
ad7d8276a2 | ||
|
|
6d652ad70b | ||
|
|
94d5b4ad92 | ||
|
|
792d2733ff | ||
|
|
07e4b5e89f | ||
|
|
fe271038cb | ||
|
|
51cb4859e3 | ||
|
|
b4667ca894 | ||
|
|
f79cfb6319 | ||
|
|
97279b7c16 | ||
|
|
5b8a64c356 | ||
|
|
29777d86e0 | ||
|
|
58440148b3 | ||
|
|
bfcf43d428 | ||
|
|
b8448d57e5 | ||
|
|
000104365c | ||
|
|
8141310f7b | ||
|
|
f432d9d187 | ||
|
|
5ab8ccc8c8 | ||
|
|
fd9818707e | ||
|
|
7422ff8624 | ||
|
|
58cf7b5bf3 | ||
|
|
87d71e1213 | ||
|
|
a0cde5352f | ||
|
|
b6ed1c047d | ||
|
|
8d8cb7381a | ||
|
|
f3bbf5e06c | ||
|
|
a5f9b42e6f | ||
|
|
d053101c14 | ||
|
|
3022b15d3a | ||
|
|
6b541e1d14 | ||
|
|
586b148ed3 | ||
|
|
cf47a758ee | ||
|
|
89230815a2 | ||
|
|
60a176bba2 | ||
|
|
44b5c010ba | ||
|
|
425c65528b | ||
|
|
8ac6f58d8e | ||
|
|
f6ac724c60 | ||
|
|
9010346c0b | ||
|
|
41a3a8f14d | ||
|
|
a883abcd1c | ||
|
|
ab37de8f40 | ||
|
|
ac0e15eaa7 | ||
|
|
a6a7e7f0c2 | ||
|
|
3cacdf9a3a | ||
|
|
3ebbda3690 | ||
|
|
74abb13acf | ||
|
|
86addc00fd | ||
|
|
9cebea225c | ||
|
|
59bdb30321 | ||
|
|
d51fb7dfa2 | ||
|
|
b955c51a91 | ||
|
|
86ecd1ad20 | ||
|
|
c089b3bdbd | ||
|
|
9612b213aa | ||
|
|
83c9b52708 | ||
|
|
a7bde7c08a | ||
|
|
c8613b5975 | ||
|
|
87bb728601 | ||
|
|
e1f9d4fb08 | ||
|
|
14e6c1186c | ||
|
|
abeb142f0b | ||
|
|
d416756614 | ||
|
|
823eb9e9a4 | ||
|
|
6579092f4a | ||
|
|
c242c09025 | ||
|
|
89c9ed598c | ||
|
|
a3a592bd16 | ||
|
|
a161829913 | ||
|
|
b4473ad067 | ||
|
|
4752f99fcf | ||
|
|
6e757cf15c | ||
|
|
a87810db88 | ||
|
|
0ddb3e8081 | ||
|
|
e29aed8bcf | ||
|
|
d99a3257af | ||
|
|
a772b210cd | ||
|
|
860700ab91 | ||
|
|
ff5d90d468 | ||
|
|
43773310d5 | ||
|
|
2da65645b0 | ||
|
|
0c62d64987 |
109
.github/workflows/build.yml
vendored
109
.github/workflows/build.yml
vendored
@@ -28,15 +28,11 @@ on:
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
build_web:
|
||||
description: 'Build for Web'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }}
|
||||
FLUTTER_VERSION: 3.38.4
|
||||
FLUTTER_VERSION_MAC: 3.41.0-0.0.pre
|
||||
FLUTTER_VERSION: 3.38.7
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -52,6 +48,9 @@ jobs:
|
||||
#1 Checkout Repository
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.PAT_TOKEN }}
|
||||
|
||||
- name: Install certificates
|
||||
if: inputs.build_mac || inputs.build_ios
|
||||
@@ -97,16 +96,17 @@ jobs:
|
||||
cp $PP_PATH_MACOS ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
|
||||
- name: 🐦 Setup Shorebird
|
||||
if: inputs.build_mac || inputs.build_android || inputs.build_ios || inputs.build_web
|
||||
#if: inputs.build_mac || inputs.build_android || inputs.build_ios
|
||||
if: inputs.build_android || inputs.build_ios
|
||||
uses: shorebirdtech/setup-shorebird@v1
|
||||
with:
|
||||
cache: true
|
||||
|
||||
- name: Set Up Flutter
|
||||
- name: Set Up Flutter maCOS
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
channel: 'beta'
|
||||
flutter-version: ${{ env.FLUTTER_VERSION_MAC }}
|
||||
|
||||
- name: Generate translation files
|
||||
run: |
|
||||
@@ -114,12 +114,23 @@ jobs:
|
||||
flutter pub global run intl_utils:generate;
|
||||
|
||||
- name: 🚀 Shorebird Release macOS
|
||||
if: inputs.build_mac
|
||||
if: inputs.build_mac && false
|
||||
uses: shorebirdtech/shorebird-release@v1
|
||||
with:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
platform: macos
|
||||
args: "-- --dart-define=VERIFYING_SHARED_SECRET=${{ secrets.VERIFYING_SHARED_SECRET }} --dart-define=REVENUECAT_API_KEY_IOS=${{ secrets.REVENUECAT_API_KEY_IOS }}"
|
||||
args: "-- --obfuscate --split-debug-info=symbols --dart-define=VERIFYING_SHARED_SECRET=${{ secrets.VERIFYING_SHARED_SECRET }} --dart-define=REVENUECAT_API_KEY_IOS=${{ secrets.REVENUECAT_API_KEY_IOS }}"
|
||||
|
||||
- name: Flutter Release macOS
|
||||
if: inputs.build_mac
|
||||
run:
|
||||
flutter build macos --release --obfuscate --split-debug-info=symbols --dart-define=VERIFYING_SHARED_SECRET=${{ secrets.VERIFYING_SHARED_SECRET }} --dart-define=REVENUECAT_API_KEY_IOS=${{ secrets.REVENUECAT_API_KEY_IOS }}
|
||||
|
||||
- name: Set Up Flutter Rest
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
|
||||
- name: Decode Keystore
|
||||
if: inputs.build_android
|
||||
@@ -133,22 +144,7 @@ jobs:
|
||||
with:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
platform: android
|
||||
args: "--artifact=apk -- --dart-define=REVENUECAT_API_KEY_ANDROID=${{ secrets.REVENUECAT_API_KEY_ANDROID }}"
|
||||
|
||||
- name: Build Web
|
||||
if: inputs.build_web
|
||||
run: flutter build web --release --base-href "/swiftcontrol/"
|
||||
|
||||
- name: Upload static files as artifact
|
||||
if: inputs.build_web
|
||||
id: deployment
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: build/web
|
||||
|
||||
- name: Web Deploy
|
||||
if: inputs.build_web
|
||||
uses: actions/deploy-pages@v4
|
||||
args: "-- --obfuscate --split-debug-info=symbols --dart-define=REVENUECAT_API_KEY_ANDROID=${{ secrets.REVENUECAT_API_KEY_ANDROID }}"
|
||||
|
||||
- name: Extract latest changelog
|
||||
id: changelog
|
||||
@@ -207,41 +203,6 @@ jobs:
|
||||
run: |
|
||||
xcrun altool --upload-app -f build/ios/ipa/swift_play.ipa -t ios --apiKey "$APPSTORE_API_KEY" --apiIssuer "$APPSTORE_API_ISSUER_ID";
|
||||
|
||||
- name: Handle Android archives
|
||||
if: inputs.build_android && inputs.build_github && false
|
||||
run: |
|
||||
cp build/app/outputs/flutter-apk/app-release.apk build/app/outputs/flutter-apk/BikeControl.android.apk
|
||||
|
||||
- name: Code Signing of macOS app
|
||||
if: inputs.build_mac && inputs.build_github && false
|
||||
run: /usr/bin/codesign --deep --force -s "$DEVELOPER_ID_APPLICATION_SIGNING_IDENTITY" --entitlements ../../../../../macos/Runner/Release.entitlements --options runtime BikeControl.app -v
|
||||
working-directory: build/macos/Build/Products/Release
|
||||
env:
|
||||
DEVELOPER_ID_APPLICATION_SIGNING_IDENTITY: ${{ secrets.DEVELOPER_ID_APPLICATION_SIGNING_IDENTITY }}
|
||||
|
||||
- name: Handle macOS archives
|
||||
if: inputs.build_mac && inputs.build_github && false
|
||||
run: |
|
||||
cd build/macos/Build/Products/Release/
|
||||
zip -r BikeControl.macos.zip BikeControl.app/
|
||||
|
||||
- name: Upload Android Artifacts
|
||||
if: inputs.build_android && inputs.build_github && false
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
overwrite: true
|
||||
name: Releases
|
||||
path: |
|
||||
build/app/outputs/flutter-apk/BikeControl.android.apk
|
||||
|
||||
- name: Upload macOS Artifacts
|
||||
if: inputs.build_mac && inputs.build_github && false
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
overwrite: true
|
||||
name: Releases
|
||||
path: |
|
||||
build/macos/Build/Products/Release/BikeControl.macos.zip
|
||||
|
||||
#10 Extract Version
|
||||
- name: Extract version from pubspec.yaml
|
||||
@@ -251,6 +212,13 @@ jobs:
|
||||
version=$(grep '^version: ' pubspec.yaml | cut -d ' ' -f 2 | tr -d '\r')
|
||||
echo "VERSION=$version" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload Symbols
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
overwrite: true
|
||||
name: Symbols
|
||||
path: symbols/
|
||||
|
||||
#13 Create Release
|
||||
- name: Create Release
|
||||
if: inputs.build_github
|
||||
@@ -272,6 +240,9 @@ jobs:
|
||||
#1 Checkout Repository
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.PAT_TOKEN }}
|
||||
|
||||
- name: Extract version from pubspec.yaml (Windows)
|
||||
shell: pwsh
|
||||
@@ -302,6 +273,7 @@ jobs:
|
||||
with:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
platform: windows
|
||||
args: "-- --obfuscate --split-debug-info=symbols-win"
|
||||
|
||||
- name: Zip directory (Windows)
|
||||
shell: pwsh
|
||||
@@ -347,14 +319,5 @@ jobs:
|
||||
overwrite: true
|
||||
name: Releases
|
||||
path: |
|
||||
build/windows/x64/runner/Release/bike_control.windows.zip
|
||||
build/windows/x64/runner/Release/bike_control.msix
|
||||
|
||||
- name: Update Release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
allowUpdates: true
|
||||
artifacts: "build/windows/x64/runner/Release/bike_control.msix"
|
||||
prerelease: true
|
||||
tag: v${{ env.VERSION }}
|
||||
token: ${{ secrets.TOKEN }}
|
||||
symbols-win/
|
||||
|
||||
6
.github/workflows/patch.yml
vendored
6
.github/workflows/patch.yml
vendored
@@ -21,6 +21,9 @@ jobs:
|
||||
#1 Checkout Repository
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.PAT_TOKEN }}
|
||||
|
||||
- name: 🐦 Setup Shorebird
|
||||
uses: shorebirdtech/setup-shorebird@v1
|
||||
@@ -159,6 +162,9 @@ jobs:
|
||||
#1 Checkout Repository
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.PAT_TOKEN }}
|
||||
|
||||
- name: 🐦 Setup Shorebird
|
||||
uses: shorebirdtech/setup-shorebird@v1
|
||||
|
||||
4
.github/workflows/web.yml
vendored
4
.github/workflows/web.yml
vendored
@@ -17,6 +17,7 @@ jobs:
|
||||
build:
|
||||
name: Build Web
|
||||
runs-on: macos-latest
|
||||
if: false
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -27,6 +28,9 @@ jobs:
|
||||
#1 Checkout Repository
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.PAT_TOKEN }}
|
||||
|
||||
#3 Setup Flutter
|
||||
- name: Set Up Flutter
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "prop"]
|
||||
path = prop
|
||||
url = git@github.com:OpenBikeControl/prop.git
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,3 +1,29 @@
|
||||
### 4.6.0 (28-01-2026)
|
||||
|
||||
**Features**:
|
||||
- Improve Zwift Click V2 connection and handling
|
||||
- Buttons in Configuration are now grouped by device
|
||||
|
||||
### 4.5.0 (22-01-2026)
|
||||
|
||||
**Features**:
|
||||
- Android: simulate additional actions for local connection method (Left, Down, Right, Up, Select, Back, Home, Recent Apps)
|
||||
- control your phone with your controller
|
||||
- control UI within the trainer app (if supported)
|
||||
- BikeControl now supports individual mapping when you use more than one Cycplus BC2 and ThinkRider VS200 controller
|
||||
- Windows & macOS: allow configuration of volume keys on Bluetooth HID devices
|
||||
|
||||
### 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
|
||||
- Windows: send keyboard events to the correct window when using multiple monitors or when another app is focused
|
||||
- Windows: fix media key detection
|
||||
|
||||
### 4.3.0 (07-01-2026)
|
||||
|
||||
**Features**:
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
## Instructions for using the MyWhoosh "Link" connection method
|
||||
*
|
||||
1) launch MyWhoosh on the device of your choice
|
||||
2) OPTIONAL: launch MyWhoosh Link, confirm the "Link" connection works at all. **close MyWhoosh Link** - very important!
|
||||
2) make sure the "MyWhoosh Link" app is not active at the same time as BikeControl
|
||||
3) open BikeControl, follow the on-screen instructions
|
||||
|
||||
Once you've confirmed the connection in BikeControl you won't have to do step 2 in the future. This is just to make sure the connection works in general.
|
||||
|
||||
And here's a video with a few explanations:
|
||||
Here's a video with a few explanations. Note it uses an older version, but the idea is the same.
|
||||
|
||||
[](https://www.youtube.com/watch?v=p8sgQhuufeI)
|
||||
[https://www.youtube.com/watch?v=p8sgQhuufeI](https://www.youtube.com/watch?v=p8sgQhuufeI)
|
||||
|
||||
## The MyWhoosh Link app itself works fine, but BikeControl doesn't connect
|
||||
*
|
||||
The MyWhoosh Link app must not run simultaneously with BikeControl. Make sure the MyWhoosh Link app is fully closed, then reopen BikeControl and try connecting again.
|
||||
|
||||
## 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 a connection is possible at all.
|
||||
Here are some instructions that can help:
|
||||
This is a network/local-discovery problem. BikeControl needs the same kind of local network access as MyWhoosh Link.
|
||||
|
||||
[https://mywhoosh.com/troubleshoot/](https://mywhoosh.com/troubleshoot/)
|
||||
[https://www.facebook.com/groups/mywhoosh/posts/1323791068858873/](https://www.facebook.com/groups/mywhoosh/posts/1323791068858873/)
|
||||
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 (if you use BikeControl on another device than MyWhoosh) are on the **same Wi‑Fi SSID**
|
||||
- Avoid “Guest” networks
|
||||
- Avoid “extenders/mesh guest mode” and networks with device isolation
|
||||
- If your router has it, disable:
|
||||
- “AP isolation / client isolation”
|
||||
- Try moving both devices to the same band:
|
||||
- Prefer **2.4 GHz** (often more reliable for local discovery than mixed/steering)
|
||||
- Temporarily disable:
|
||||
- VPNs
|
||||
- iCloud Private Relay (if enabled)
|
||||
- “Limit IP Address Tracking” (iOS Wi‑Fi option)
|
||||
- iOS Wi‑Fi settings for that network:
|
||||
- Turn off **Private Wi‑Fi Address**
|
||||
- Turn off **Limit IP Address Tracking**
|
||||
- Mesh networks: may work, but if it doesn’t, test with a simple router or phone hotspot.
|
||||
|
||||
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
|
||||
Official MyWhoosh troubleshooting links:
|
||||
- https://mywhoosh.com/troubleshoot/
|
||||
- https://www.facebook.com/groups/mywhoosh/posts/1323791068858873/
|
||||
|
||||
@@ -11,10 +11,7 @@ With BikeControl you can **control your favorite trainer app** using your Zwift
|
||||
- control music on your device
|
||||
- more? If you can do it via keyboard, mouse, or touch, you can do it with BikeControl
|
||||
|
||||
|
||||
https://github.com/user-attachments/assets/1f81b674-1628-4763-ad66-5f3ed7a3f159
|
||||
|
||||
|
||||
[](https://www.youtube.com/watch?v=rK6Zthcl2UU)
|
||||
|
||||
|
||||
## Download
|
||||
@@ -31,7 +28,7 @@ Best follow our landing page and the "Get Started" button: [bikecontrol.app](htt
|
||||
## Supported Apps
|
||||
- MyWhoosh
|
||||
- Zwift
|
||||
- TrainingPeaks Virtual / indieVelo
|
||||
- TrainingPeaks Virtual
|
||||
- Biketerra.com
|
||||
- Rouvy
|
||||
- [OpenBikeControl](https://openbikecontrol.org) compatible apps
|
||||
@@ -51,6 +48,7 @@ Best follow our landing page and the "Get Started" button: [bikecontrol.app](htt
|
||||
- Wahoo Kickr Bike Shift
|
||||
- Wahoo Kickr Bike Pro
|
||||
- CYCPLUS BC2 Virtual Shifter
|
||||
- Thinkrider VS200 Virtual Shifter (beta)
|
||||
- Elite Sterzo Smart (for steering support)
|
||||
- Elite Square Smart Frame (beta)
|
||||
- Your Phone!
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
## Click / Ride device cannot be found
|
||||
*
|
||||
You may need to update the firmware in Zwift Companion app.
|
||||
This means BikeControl does NOT see the device via Bluetooth.
|
||||
- Put the controller into pairing mode (LED should blink)
|
||||
- Ensure the controller is NOT connected to another app/device (e.g. Zwift)
|
||||
- Update controller firmware in Zwift Companion, if available
|
||||
- Reboot Bluetooth / reboot phone/PC
|
||||
|
||||
## 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
|
||||
## My Click v2 disconnects after a minute or buttons do not work
|
||||
*
|
||||
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.
|
||||
To make your Click V2 work best you should connect it in the Zwift app once before a workout session.
|
||||
If you don't do that BikeControl will need to reconnect every minute.
|
||||
|
||||
1. Open Zwift app (not the Companion)
|
||||
2. Log in (subscription not required) and open the device connection screen
|
||||
3. Connect your Trainer, then connect the Click V2
|
||||
4. Optional: some users report that keeping the Click connected for more than a few seconds is more reliable.
|
||||
5. Close the Zwift app again and connect again in BikeControl
|
||||
2. Log in (subscription not required) → device connection screen
|
||||
3. Connect trainer, then connect Click v2
|
||||
4. Keep it connected for ~10–30 seconds
|
||||
5. Close Zwift completely, then connect in BikeControl
|
||||
|
||||
Details/updates: https://github.com/jonasbark/swiftcontrol/issues/68
|
||||
|
||||
## Android: Connection works, buttons work but nothing happens in MyWhoosh and similar
|
||||
*
|
||||
@@ -27,10 +32,6 @@ If you don't do that BikeControl will need to reconnect every minute.
|
||||
- grant accessibility permission for BikeControl
|
||||
- see [https://github.com/jonasbark/swiftcontrol/issues/38](https://github.com/OpenBikeControl/bikecontrol/issues/38) for more details
|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
## My Clicks do not get recognized in MyWhoosh, but I am connected / use local control
|
||||
*
|
||||
|
||||
@@ -1 +1 @@
|
||||
4.2.2
|
||||
4.4.1
|
||||
|
||||
@@ -90,6 +90,23 @@ enum class MediaAction(val raw: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
enum class GlobalAction(val raw: Int) {
|
||||
BACK(0),
|
||||
DPAD_CENTER(1),
|
||||
DOWN(2),
|
||||
RIGHT(3),
|
||||
UP(4),
|
||||
LEFT(5),
|
||||
HOME(6),
|
||||
RECENTS(7);
|
||||
|
||||
companion object {
|
||||
fun ofRaw(raw: Int): GlobalAction? {
|
||||
return values().firstOrNull { it.raw == raw }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Generated class from Pigeon that represents data sent in messages. */
|
||||
data class WindowEvent (
|
||||
val packageName: String,
|
||||
@@ -175,11 +192,16 @@ private open class AccessibilityApiPigeonCodec : StandardMessageCodec() {
|
||||
}
|
||||
}
|
||||
130.toByte() -> {
|
||||
return (readValue(buffer) as Long?)?.let {
|
||||
GlobalAction.ofRaw(it.toInt())
|
||||
}
|
||||
}
|
||||
131.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
WindowEvent.fromList(it)
|
||||
}
|
||||
}
|
||||
131.toByte() -> {
|
||||
132.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
AKeyEvent.fromList(it)
|
||||
}
|
||||
@@ -193,12 +215,16 @@ private open class AccessibilityApiPigeonCodec : StandardMessageCodec() {
|
||||
stream.write(129)
|
||||
writeValue(stream, value.raw)
|
||||
}
|
||||
is WindowEvent -> {
|
||||
is GlobalAction -> {
|
||||
stream.write(130)
|
||||
writeValue(stream, value.raw)
|
||||
}
|
||||
is WindowEvent -> {
|
||||
stream.write(131)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is AKeyEvent -> {
|
||||
stream.write(131)
|
||||
stream.write(132)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
else -> super.writeValue(stream, value)
|
||||
@@ -213,9 +239,11 @@ interface Accessibility {
|
||||
fun hasPermission(): Boolean
|
||||
fun openPermissions()
|
||||
fun performTouch(x: Double, y: Double, isKeyDown: Boolean, isKeyUp: Boolean)
|
||||
fun performGlobalAction(action: GlobalAction)
|
||||
fun controlMedia(action: MediaAction)
|
||||
fun isRunning(): Boolean
|
||||
fun ignoreHidDevices()
|
||||
fun setHandledKeys(keys: List<String>)
|
||||
|
||||
companion object {
|
||||
/** The codec used by Accessibility. */
|
||||
@@ -278,6 +306,24 @@ interface Accessibility {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.accessibility.Accessibility.performGlobalAction$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val actionArg = args[0] as GlobalAction
|
||||
val wrapped: List<Any?> = try {
|
||||
api.performGlobalAction(actionArg)
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
AccessibilityApiPigeonUtils.wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.accessibility.Accessibility.controlMedia$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
@@ -327,6 +373,24 @@ interface Accessibility {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.accessibility.Accessibility.setHandledKeys$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val keysArg = args[0] as List<String>
|
||||
val wrapped: List<Any?> = try {
|
||||
api.setHandledKeys(keysArg)
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
AccessibilityApiPigeonUtils.wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package de.jonasbark.accessibility
|
||||
|
||||
import AKeyEvent
|
||||
import Accessibility
|
||||
import GlobalAction
|
||||
import HidKeyPressedStreamHandler
|
||||
import MediaAction
|
||||
import PigeonEventSink
|
||||
@@ -66,6 +67,10 @@ class AccessibilityPlugin: FlutterPlugin, Accessibility {
|
||||
Observable.toService?.performTouch(x = x, y = y, isKeyUp = isKeyUp, isKeyDown = isKeyDown) ?: error("Service not running")
|
||||
}
|
||||
|
||||
override fun performGlobalAction(action: GlobalAction) {
|
||||
Observable.toService?.performGlobalAction(action) ?: error("Service not running")
|
||||
}
|
||||
|
||||
override fun controlMedia(action: MediaAction) {
|
||||
val audioService = context.getSystemService(Context.AUDIO_SERVICE) as android.media.AudioManager
|
||||
when (action) {
|
||||
@@ -90,6 +95,11 @@ class AccessibilityPlugin: FlutterPlugin, Accessibility {
|
||||
Observable.ignoreHidDevices = true
|
||||
}
|
||||
|
||||
override fun setHandledKeys(keys: List<String>) {
|
||||
// Clear and update the concurrent set
|
||||
Observable.handledKeys = keys.toSet()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class WindowEventListener : StreamEventsStreamHandler(), Receiver {
|
||||
|
||||
@@ -15,6 +15,7 @@ import android.view.KeyEvent
|
||||
import android.view.ViewConfiguration
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
|
||||
import GlobalAction
|
||||
|
||||
|
||||
class AccessibilityService : AccessibilityService(), Listener {
|
||||
@@ -70,8 +71,10 @@ class AccessibilityService : AccessibilityService(), Listener {
|
||||
}
|
||||
|
||||
override fun onKeyEvent(event: KeyEvent): Boolean {
|
||||
if (!Observable.ignoreHidDevices && isBleRemote(event)) {
|
||||
// Handle media and volume keys from HID devices here
|
||||
val keyString = KeyEvent.keyCodeToString(event.keyCode)
|
||||
// if currently active app is BikeControl => handle it, so keymap can be created
|
||||
if (!Observable.ignoreHidDevices && isBleRemote(event) && (rootInActiveWindow.packageName == "de.jonasbark.swiftcontrol" || Observable.handledKeys.contains(keyString))) {
|
||||
// Handle keys that have a keymap defined
|
||||
Log.d(
|
||||
"AccessibilityService",
|
||||
"onKeyEvent: keyCode=${event.keyCode} action=${event.action} scanCode=${event.scanCode} flags=${event.flags}"
|
||||
@@ -95,6 +98,20 @@ class AccessibilityService : AccessibilityService(), Listener {
|
||||
}
|
||||
}
|
||||
|
||||
override fun performGlobalAction(action: GlobalAction) {
|
||||
val mappedAction = when (action) {
|
||||
GlobalAction.BACK -> android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_BACK
|
||||
GlobalAction.DPAD_CENTER -> android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_DPAD_CENTER
|
||||
GlobalAction.DOWN -> android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_DPAD_DOWN
|
||||
GlobalAction.RIGHT -> android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_DPAD_RIGHT
|
||||
GlobalAction.UP -> android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_DPAD_UP
|
||||
GlobalAction.LEFT -> android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_DPAD_LEFT
|
||||
GlobalAction.HOME -> android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_HOME
|
||||
GlobalAction.RECENTS -> android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_RECENTS
|
||||
}
|
||||
performGlobalAction(mappedAction)
|
||||
}
|
||||
|
||||
override fun performTouch(x: Double, y: Double, isKeyDown: Boolean, isKeyUp: Boolean) {
|
||||
val gestureBuilder = GestureDescription.Builder()
|
||||
val path = Path()
|
||||
|
||||
@@ -2,16 +2,21 @@ package de.jonasbark.accessibility
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.KeyEvent
|
||||
import GlobalAction
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
object Observable {
|
||||
var toService: Listener? = null
|
||||
var fromServiceWindow: Receiver? = null
|
||||
var fromServiceKeys: Receiver? = null
|
||||
var ignoreHidDevices: Boolean = false
|
||||
// Use concurrent set for thread-safe access from AccessibilityService and plugin
|
||||
var handledKeys: Set<String> = ConcurrentHashMap.newKeySet()
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun performTouch(x: Double, y: Double, isKeyDown: Boolean, isKeyUp: Boolean)
|
||||
fun performGlobalAction(action: GlobalAction)
|
||||
}
|
||||
|
||||
interface Receiver {
|
||||
|
||||
@@ -8,15 +8,30 @@ abstract class Accessibility {
|
||||
|
||||
void performTouch(double x, double y, {bool isKeyDown = true, bool isKeyUp = false});
|
||||
|
||||
void performGlobalAction(GlobalAction action);
|
||||
|
||||
void controlMedia(MediaAction action);
|
||||
|
||||
bool isRunning();
|
||||
|
||||
void ignoreHidDevices();
|
||||
|
||||
void setHandledKeys(List<String> keys);
|
||||
}
|
||||
|
||||
enum MediaAction { playPause, next, volumeUp, volumeDown }
|
||||
|
||||
enum GlobalAction {
|
||||
back,
|
||||
dpadCenter,
|
||||
down,
|
||||
right,
|
||||
up,
|
||||
left,
|
||||
home,
|
||||
recents,
|
||||
}
|
||||
|
||||
class WindowEvent {
|
||||
final String packageName;
|
||||
final int top;
|
||||
|
||||
@@ -36,6 +36,17 @@ enum MediaAction {
|
||||
volumeDown,
|
||||
}
|
||||
|
||||
enum GlobalAction {
|
||||
back,
|
||||
dpadCenter,
|
||||
down,
|
||||
right,
|
||||
up,
|
||||
left,
|
||||
home,
|
||||
recents,
|
||||
}
|
||||
|
||||
class WindowEvent {
|
||||
WindowEvent({
|
||||
required this.packageName,
|
||||
@@ -164,11 +175,14 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||
} else if (value is MediaAction) {
|
||||
buffer.putUint8(129);
|
||||
writeValue(buffer, value.index);
|
||||
} else if (value is WindowEvent) {
|
||||
} else if (value is GlobalAction) {
|
||||
buffer.putUint8(130);
|
||||
writeValue(buffer, value.index);
|
||||
} else if (value is WindowEvent) {
|
||||
buffer.putUint8(131);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is AKeyEvent) {
|
||||
buffer.putUint8(131);
|
||||
buffer.putUint8(132);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
super.writeValue(buffer, value);
|
||||
@@ -182,8 +196,11 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||
final int? value = readValue(buffer) as int?;
|
||||
return value == null ? null : MediaAction.values[value];
|
||||
case 130:
|
||||
return WindowEvent.decode(readValue(buffer)!);
|
||||
final int? value = readValue(buffer) as int?;
|
||||
return value == null ? null : GlobalAction.values[value];
|
||||
case 131:
|
||||
return WindowEvent.decode(readValue(buffer)!);
|
||||
case 132:
|
||||
return AKeyEvent.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
@@ -280,6 +297,29 @@ class Accessibility {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> performGlobalAction(GlobalAction action) async {
|
||||
final String pigeonVar_channelName = 'dev.flutter.pigeon.accessibility.Accessibility.performGlobalAction$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[action]);
|
||||
final List<Object?>? pigeonVar_replyList =
|
||||
await pigeonVar_sendFuture as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
} else if (pigeonVar_replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: pigeonVar_replyList[0]! as String,
|
||||
message: pigeonVar_replyList[1] as String?,
|
||||
details: pigeonVar_replyList[2],
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> controlMedia(MediaAction action) async {
|
||||
final String pigeonVar_channelName = 'dev.flutter.pigeon.accessibility.Accessibility.controlMedia$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
@@ -353,6 +393,29 @@ class Accessibility {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setHandledKeys(List<String> keys) async {
|
||||
final String pigeonVar_channelName = 'dev.flutter.pigeon.accessibility.Accessibility.setHandledKeys$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[keys]);
|
||||
final List<Object?>? pigeonVar_replyList =
|
||||
await pigeonVar_sendFuture as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
} else if (pigeonVar_replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: pigeonVar_replyList[0]! as String,
|
||||
message: pigeonVar_replyList[1] as String?,
|
||||
details: pigeonVar_replyList[2],
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Stream<WindowEvent> streamEvents( {String instanceName = ''}) {
|
||||
|
||||
@@ -16,7 +16,7 @@ keystoreProperties.load(FileInputStream(keystorePropertiesFile))
|
||||
android {
|
||||
namespace = "de.jonasbark.swiftcontrol"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = "27.0.12077973"
|
||||
ndkVersion = "28.2.13676358"
|
||||
|
||||
compileOptions {
|
||||
// Flag to enable support for the new language APIs
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
|
||||
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
|
||||
<!-- New Bluetooth permissions in Android 12
|
||||
https://developer.android.com/about/versions/12/features/bluetooth-permissions -->
|
||||
|
||||
BIN
android/app/src/main/res/drawable-hdpi/ic_notification.png
Normal file
BIN
android/app/src/main/res/drawable-hdpi/ic_notification.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 785 B |
BIN
android/app/src/main/res/drawable-mdpi/ic_notification.png
Normal file
BIN
android/app/src/main/res/drawable-mdpi/ic_notification.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 476 B |
BIN
android/app/src/main/res/drawable-xhdpi/ic_notification.png
Normal file
BIN
android/app/src/main/res/drawable-xhdpi/ic_notification.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
android/app/src/main/res/drawable-xxhdpi/ic_notification.png
Normal file
BIN
android/app/src/main/res/drawable-xxhdpi/ic_notification.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/ic_notification.png
Normal file
BIN
android/app/src/main/res/drawable-xxxhdpi/ic_notification.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
3
android/app/src/main/res/raw/keep.xml
Normal file
3
android/app/src/main/res/raw/keep.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:keep="@drawable/*" />
|
||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
|
||||
@@ -18,7 +18,7 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.7.3" apply false
|
||||
id("com.android.application") version "8.11.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ PODS:
|
||||
- flutter_secure_storage_darwin (10.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- flutter_volume_controller (0.0.1):
|
||||
- Flutter
|
||||
- gamepads_ios (0.1.1):
|
||||
- Flutter
|
||||
- image_picker_ios (0.0.1):
|
||||
@@ -29,27 +31,24 @@ PODS:
|
||||
- Flutter
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- purchases_flutter (9.10.2):
|
||||
- purchases_flutter (9.10.6):
|
||||
- Flutter
|
||||
- PurchasesHybridCommon (= 17.25.0)
|
||||
- purchases_ui_flutter (9.10.2):
|
||||
- PurchasesHybridCommon (= 17.27.1)
|
||||
- purchases_ui_flutter (9.10.6):
|
||||
- Flutter
|
||||
- PurchasesHybridCommonUI (= 17.25.0)
|
||||
- PurchasesHybridCommon (17.25.0):
|
||||
- RevenueCat (= 5.51.1)
|
||||
- PurchasesHybridCommonUI (17.25.0):
|
||||
- PurchasesHybridCommon (= 17.25.0)
|
||||
- RevenueCatUI (= 5.51.1)
|
||||
- PurchasesHybridCommonUI (= 17.27.1)
|
||||
- PurchasesHybridCommon (17.27.1):
|
||||
- RevenueCat (= 5.54.1)
|
||||
- PurchasesHybridCommonUI (17.27.1):
|
||||
- PurchasesHybridCommon (= 17.27.1)
|
||||
- RevenueCatUI (= 5.54.1)
|
||||
- restart_app (0.0.1):
|
||||
- Flutter
|
||||
- RevenueCat (5.51.1)
|
||||
- RevenueCatUI (5.51.1):
|
||||
- RevenueCat (= 5.51.1)
|
||||
- RevenueCat (5.54.1)
|
||||
- RevenueCatUI (5.54.1):
|
||||
- RevenueCat (= 5.54.1)
|
||||
- sensors_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
@@ -69,6 +68,7 @@ DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||
- flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
|
||||
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
|
||||
- gamepads_ios (from `.symlinks/plugins/gamepads_ios/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`)
|
||||
@@ -78,7 +78,6 @@ DEPENDENCIES:
|
||||
- media_key_detector_ios (from `.symlinks/plugins/media_key_detector_ios/ios`)
|
||||
- nsd_ios (from `.symlinks/plugins/nsd_ios/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- purchases_flutter (from `.symlinks/plugins/purchases_flutter/ios`)
|
||||
- purchases_ui_flutter (from `.symlinks/plugins/purchases_ui_flutter/ios`)
|
||||
@@ -107,6 +106,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||
flutter_secure_storage_darwin:
|
||||
:path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin"
|
||||
flutter_volume_controller:
|
||||
:path: ".symlinks/plugins/flutter_volume_controller/ios"
|
||||
gamepads_ios:
|
||||
:path: ".symlinks/plugins/gamepads_ios/ios"
|
||||
image_picker_ios:
|
||||
@@ -125,8 +126,6 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/nsd_ios/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
purchases_flutter:
|
||||
@@ -147,13 +146,14 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
bluetooth_low_energy_darwin: 764d8d1ae5abefbcdb839e812b4b25c0061fcf8b
|
||||
bluetooth_low_energy_darwin: 50bc79258e60586e4c4bed5948bd31d925f37fac
|
||||
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f
|
||||
flutter_secure_storage_darwin: 557817588b80e60213cbecb573c45c76b788018d
|
||||
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
|
||||
gamepads_ios: 1d2930c7a4450a9a1b57444ebf305a6a6cbeea0b
|
||||
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
|
||||
in_app_purchase_storekit: 2342c0a5da86593124d08dd13d920f39a52b273a
|
||||
in_app_review: 436034b18594851a7328d7f1c2ed5ec235b79cfc
|
||||
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
|
||||
@@ -161,19 +161,18 @@ SPEC CHECKSUMS:
|
||||
media_key_detector_ios: 7ff9aefdfea00bb7b71e184132381b7d0e7e1269
|
||||
nsd_ios: 8c37babdc6538e3350dbed3a52674d2edde98173
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
purchases_flutter: 7cbb87481a018c1bb8b4966b7d446a7c19d96d87
|
||||
purchases_ui_flutter: 1905cebf3f46e03aeefba0f6258000f3cdd32641
|
||||
PurchasesHybridCommon: 6a79a873ab52f777bfa36e9516f3fcd84d3b3428
|
||||
PurchasesHybridCommonUI: 3c1f78addfb3f470713548b4eac8c59254d0efe6
|
||||
purchases_flutter: b3c0792197f69cd7af4c2449b71df6ac6378aace
|
||||
purchases_ui_flutter: caae6d62ea23c6fe964992a28353211cc74b244a
|
||||
PurchasesHybridCommon: 027f03312519c51056457eb2e4f7ee1c91b61b8f
|
||||
PurchasesHybridCommonUI: 48afb5e29204958bff1276b0f7acb8e4b59fe99a
|
||||
restart_app: 806659942bf932f6ce51c5372f91ce5e81c8c14a
|
||||
RevenueCat: eab035bbab271faccfef5c36eaff2a1ffef14dc0
|
||||
RevenueCatUI: c6acb3648fa58ccb4f0f178556ed0278b796234f
|
||||
RevenueCat: ecbba580fa453b0d4a0475449b904196d74ef678
|
||||
RevenueCatUI: ac7492873928e9e7f297e5e27a7c4f23f9008326
|
||||
sensors_plus: 7229095999f30740798f0eeef5cd120357a8f4f2
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
|
||||
universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa
|
||||
wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e
|
||||
|
||||
PODFILE CHECKSUM: 7ebd5c9b932b3af79d5c67e3af873118b74e970f
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
#include "keypress_simulator_windows_plugin.h"
|
||||
|
||||
// This must be included before many other Windows headers.
|
||||
#include <windows.h>
|
||||
#include <flutter_windows.h>
|
||||
#include <psapi.h>
|
||||
#include <string.h>
|
||||
#include <flutter_windows.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <flutter/method_channel.h>
|
||||
#include <flutter/plugin_registrar_windows.h>
|
||||
#include <flutter/standard_method_codec.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
using flutter::EncodableList;
|
||||
using flutter::EncodableMap;
|
||||
@@ -28,7 +30,8 @@ struct FindWindowData {
|
||||
};
|
||||
|
||||
BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam);
|
||||
HWND FindTargetWindow(const std::string& processName, const std::string& windowTitle);
|
||||
HWND FindTargetWindow(const std::string& processName,
|
||||
const std::string& windowTitle);
|
||||
|
||||
// static
|
||||
void KeypressSimulatorWindowsPlugin::RegisterWithRegistrar(
|
||||
@@ -69,26 +72,91 @@ void KeypressSimulatorWindowsPlugin::SimulateKeyPress(
|
||||
}
|
||||
|
||||
// List of compatible training apps to look for
|
||||
std::vector<std::string> compatibleApps = {
|
||||
"MyWhooshHD.exe",
|
||||
"indieVelo.exe",
|
||||
"biketerra.exe"
|
||||
};
|
||||
std::vector<std::string> compatibleApps = {"MyWhooshHD.exe", "MyWhoosh.exe",
|
||||
"indieVelo.exe", "biketerra.exe",
|
||||
"Rouvy.exe"};
|
||||
|
||||
// Try to find and focus a compatible app
|
||||
// Try to find and focus (or directly target) a compatible app
|
||||
std::string foundProcessName;
|
||||
bool supportsBackgroundInput = true;
|
||||
HWND targetWindow = NULL;
|
||||
for (const std::string& processName : compatibleApps) {
|
||||
targetWindow = FindTargetWindow(processName, "");
|
||||
if (targetWindow != NULL) {
|
||||
// Only focus the window if it's not already in the foreground
|
||||
if (GetForegroundWindow() != targetWindow) {
|
||||
foundProcessName = processName;
|
||||
if (!supportsBackgroundInput && GetForegroundWindow() != targetWindow) {
|
||||
SetForegroundWindow(targetWindow);
|
||||
Sleep(50); // Brief delay to ensure window is focused
|
||||
Sleep(50); // Brief delay to ensure window is focused
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a target window that supports background input and it's not
|
||||
// focused, send messages directly
|
||||
auto postKeyMessage = [](HWND hwnd, UINT vkCode, bool down) {
|
||||
const WORD scanCode =
|
||||
static_cast<WORD>(MapVirtualKey(vkCode, MAPVK_VK_TO_VSC));
|
||||
// Build lParam with repeat count 1 and scan code; set transition states for
|
||||
// key up
|
||||
LPARAM lParam = 1 | (static_cast<LPARAM>(scanCode) << 16);
|
||||
if (vkCode == VK_LEFT || vkCode == VK_RIGHT || vkCode == VK_UP ||
|
||||
vkCode == VK_DOWN || vkCode == VK_INSERT || vkCode == VK_DELETE ||
|
||||
vkCode == VK_HOME || vkCode == VK_END || vkCode == VK_PRIOR ||
|
||||
vkCode == VK_NEXT) {
|
||||
lParam |= (1 << 24); // extended key
|
||||
}
|
||||
if (!down) {
|
||||
lParam |= (1 << 30); // previous key state
|
||||
lParam |= (1 << 31); // transition state
|
||||
}
|
||||
PostMessage(hwnd, down ? WM_KEYDOWN : WM_KEYUP, vkCode, lParam);
|
||||
};
|
||||
|
||||
auto sendKeyToWindow = [&postKeyMessage](HWND hwnd,
|
||||
const std::vector<std::string>& mods,
|
||||
UINT keyCode, bool down) {
|
||||
auto handleModifier = [&postKeyMessage, hwnd](UINT vk, bool press) {
|
||||
postKeyMessage(hwnd, vk, press);
|
||||
};
|
||||
|
||||
if (down) {
|
||||
for (const std::string& modifier : mods) {
|
||||
if (modifier == "shiftModifier") {
|
||||
handleModifier(VK_SHIFT, true);
|
||||
} else if (modifier == "controlModifier") {
|
||||
handleModifier(VK_CONTROL, true);
|
||||
} else if (modifier == "altModifier") {
|
||||
handleModifier(VK_MENU, true);
|
||||
} else if (modifier == "metaModifier") {
|
||||
handleModifier(VK_LWIN, true);
|
||||
}
|
||||
}
|
||||
postKeyMessage(hwnd, keyCode, true);
|
||||
} else {
|
||||
postKeyMessage(hwnd, keyCode, false);
|
||||
// release modifiers
|
||||
for (const std::string& modifier : mods) {
|
||||
if (modifier == "shiftModifier") {
|
||||
handleModifier(VK_SHIFT, false);
|
||||
} else if (modifier == "controlModifier") {
|
||||
handleModifier(VK_CONTROL, false);
|
||||
} else if (modifier == "altModifier") {
|
||||
handleModifier(VK_MENU, false);
|
||||
} else if (modifier == "metaModifier") {
|
||||
handleModifier(VK_LWIN, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (targetWindow != NULL && !foundProcessName.empty() &&
|
||||
supportsBackgroundInput && GetForegroundWindow() != targetWindow) {
|
||||
sendKeyToWindow(targetWindow, modifiers, keyCode, keyDown);
|
||||
result->Success(flutter::EncodableValue(true));
|
||||
return;
|
||||
}
|
||||
|
||||
// Helper function to send modifier key events
|
||||
auto sendModifierKey = [](UINT vkCode, bool down) {
|
||||
WORD sc = (WORD)MapVirtualKey(vkCode, MAPVK_VK_TO_VSC);
|
||||
@@ -101,7 +169,8 @@ void KeypressSimulatorWindowsPlugin::SimulateKeyPress(
|
||||
};
|
||||
|
||||
// Helper function to process modifiers
|
||||
auto processModifiers = [&sendModifierKey](const std::vector<std::string>& mods, bool down) {
|
||||
auto processModifiers = [&sendModifierKey](
|
||||
const std::vector<std::string>& mods, bool down) {
|
||||
for (const std::string& modifier : mods) {
|
||||
if (modifier == "shiftModifier") {
|
||||
sendModifierKey(VK_SHIFT, down);
|
||||
@@ -125,12 +194,13 @@ void KeypressSimulatorWindowsPlugin::SimulateKeyPress(
|
||||
|
||||
INPUT in = {0};
|
||||
in.type = INPUT_KEYBOARD;
|
||||
in.ki.wVk = 0; // when using SCANCODE, set VK=0
|
||||
in.ki.wVk = 0; // when using SCANCODE, set VK=0
|
||||
in.ki.wScan = sc;
|
||||
in.ki.dwFlags = KEYEVENTF_SCANCODE | (keyDown ? 0 : KEYEVENTF_KEYUP);
|
||||
if (keyCode == VK_LEFT || keyCode == VK_RIGHT || keyCode == VK_UP || keyCode == VK_DOWN ||
|
||||
keyCode == VK_INSERT || keyCode == VK_DELETE || keyCode == VK_HOME || keyCode == VK_END ||
|
||||
keyCode == VK_PRIOR || keyCode == VK_NEXT) {
|
||||
if (keyCode == VK_LEFT || keyCode == VK_RIGHT || keyCode == VK_UP ||
|
||||
keyCode == VK_DOWN || keyCode == VK_INSERT || keyCode == VK_DELETE ||
|
||||
keyCode == VK_HOME || keyCode == VK_END || keyCode == VK_PRIOR ||
|
||||
keyCode == VK_NEXT) {
|
||||
in.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
|
||||
}
|
||||
SendInput(1, &in, sizeof(INPUT));
|
||||
@@ -149,7 +219,6 @@ void KeypressSimulatorWindowsPlugin::SimulateKeyPress(
|
||||
void KeypressSimulatorWindowsPlugin::SimulateMouseClick(
|
||||
const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
|
||||
|
||||
const EncodableMap& args = std::get<EncodableMap>(*method_call.arguments());
|
||||
double x = 0;
|
||||
double y = 0;
|
||||
@@ -157,12 +226,12 @@ void KeypressSimulatorWindowsPlugin::SimulateMouseClick(
|
||||
bool keyDown = std::get<bool>(args.at(EncodableValue("keyDown")));
|
||||
auto it_x = args.find(EncodableValue("x"));
|
||||
if (it_x != args.end() && std::holds_alternative<double>(it_x->second)) {
|
||||
x = std::get<double>(it_x->second);
|
||||
x = std::get<double>(it_x->second);
|
||||
}
|
||||
|
||||
auto it_y = args.find(EncodableValue("y"));
|
||||
if (it_y != args.end() && std::holds_alternative<double>(it_y->second)) {
|
||||
y = std::get<double>(it_y->second);
|
||||
y = std::get<double>(it_y->second);
|
||||
}
|
||||
|
||||
// Get the monitor containing the target point and its DPI
|
||||
@@ -170,7 +239,7 @@ void KeypressSimulatorWindowsPlugin::SimulateMouseClick(
|
||||
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
|
||||
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
|
||||
double scale_factor = dpi / 96.0;
|
||||
|
||||
|
||||
// Scale the coordinates according to the DPI scaling
|
||||
int scaled_x = static_cast<int>(x * scale_factor);
|
||||
int scaled_y = static_cast<int>(y * scale_factor);
|
||||
@@ -183,14 +252,14 @@ void KeypressSimulatorWindowsPlugin::SimulateMouseClick(
|
||||
input.type = INPUT_MOUSE;
|
||||
|
||||
if (keyDown) {
|
||||
// Mouse left button down
|
||||
input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
|
||||
SendInput(1, &input, sizeof(INPUT));
|
||||
// Mouse left button down
|
||||
input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
|
||||
SendInput(1, &input, sizeof(INPUT));
|
||||
|
||||
} else {
|
||||
// Mouse left button up
|
||||
input.mi.dwFlags = MOUSEEVENTF_LEFTUP;
|
||||
SendInput(1, &input, sizeof(INPUT));
|
||||
// Mouse left button up
|
||||
input.mi.dwFlags = MOUSEEVENTF_LEFTUP;
|
||||
SendInput(1, &input, sizeof(INPUT));
|
||||
}
|
||||
|
||||
result->Success(flutter::EncodableValue(true));
|
||||
@@ -201,7 +270,7 @@ BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam) {
|
||||
|
||||
// Check if window is visible and not minimized
|
||||
if (!IsWindowVisible(hwnd) || IsIconic(hwnd)) {
|
||||
return TRUE; // Continue enumeration
|
||||
return TRUE; // Continue enumeration
|
||||
}
|
||||
|
||||
// Get window title
|
||||
@@ -211,7 +280,8 @@ BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam) {
|
||||
// Get process name
|
||||
DWORD processId;
|
||||
GetWindowThreadProcessId(hwnd, &processId);
|
||||
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processId);
|
||||
HANDLE hProcess =
|
||||
OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processId);
|
||||
char processName[MAX_PATH];
|
||||
if (hProcess) {
|
||||
DWORD size = sizeof(processName);
|
||||
@@ -219,7 +289,7 @@ BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam) {
|
||||
// Extract just the filename from the full path
|
||||
char* filename = strrchr(processName, '\\');
|
||||
if (filename) {
|
||||
filename++; // Skip the backslash
|
||||
filename++; // Skip the backslash
|
||||
} else {
|
||||
filename = processName;
|
||||
}
|
||||
@@ -228,7 +298,7 @@ BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam) {
|
||||
if (!data->targetProcessName.empty() &&
|
||||
_stricmp(filename, data->targetProcessName.c_str()) == 0) {
|
||||
data->foundWindow = hwnd;
|
||||
return FALSE; // Stop enumeration
|
||||
return FALSE; // Stop enumeration
|
||||
}
|
||||
}
|
||||
CloseHandle(hProcess);
|
||||
@@ -238,13 +308,14 @@ BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam) {
|
||||
if (!data->targetWindowTitle.empty() &&
|
||||
_stricmp(windowTitle, data->targetWindowTitle.c_str()) == 0) {
|
||||
data->foundWindow = hwnd;
|
||||
return FALSE; // Stop enumeration
|
||||
return FALSE; // Stop enumeration
|
||||
}
|
||||
|
||||
return TRUE; // Continue enumeration
|
||||
return TRUE; // Continue enumeration
|
||||
}
|
||||
|
||||
HWND FindTargetWindow(const std::string& processName, const std::string& windowTitle) {
|
||||
HWND FindTargetWindow(const std::string& processName,
|
||||
const std::string& windowTitle) {
|
||||
FindWindowData data;
|
||||
data.targetProcessName = processName;
|
||||
data.targetWindowTitle = windowTitle;
|
||||
@@ -257,19 +328,15 @@ HWND FindTargetWindow(const std::string& processName, const std::string& windowT
|
||||
void KeypressSimulatorWindowsPlugin::SimulateMediaKey(
|
||||
const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
|
||||
|
||||
const EncodableMap& args = std::get<EncodableMap>(*method_call.arguments());
|
||||
std::string keyIdentifier = std::get<std::string>(args.at(EncodableValue("key")));
|
||||
std::string keyIdentifier =
|
||||
std::get<std::string>(args.at(EncodableValue("key")));
|
||||
|
||||
// Map string identifier to Windows virtual key codes
|
||||
static const std::unordered_map<std::string, UINT> keyMap = {
|
||||
{"playPause", VK_MEDIA_PLAY_PAUSE},
|
||||
{"stop", VK_MEDIA_STOP},
|
||||
{"next", VK_MEDIA_NEXT_TRACK},
|
||||
{"previous", VK_MEDIA_PREV_TRACK},
|
||||
{"volumeUp", VK_VOLUME_UP},
|
||||
{"volumeDown", VK_VOLUME_DOWN}
|
||||
};
|
||||
{"playPause", VK_MEDIA_PLAY_PAUSE}, {"stop", VK_MEDIA_STOP},
|
||||
{"next", VK_MEDIA_NEXT_TRACK}, {"previous", VK_MEDIA_PREV_TRACK},
|
||||
{"volumeUp", VK_VOLUME_UP}, {"volumeDown", VK_VOLUME_DOWN}};
|
||||
|
||||
auto it = keyMap.find(keyIdentifier);
|
||||
if (it == keyMap.end()) {
|
||||
@@ -282,7 +349,7 @@ void KeypressSimulatorWindowsPlugin::SimulateMediaKey(
|
||||
INPUT inputs[2] = {};
|
||||
inputs[0].type = INPUT_KEYBOARD;
|
||||
inputs[0].ki.wVk = static_cast<WORD>(vkCode);
|
||||
inputs[0].ki.dwFlags = 0; // Key down
|
||||
inputs[0].ki.dwFlags = 0; // Key down
|
||||
|
||||
// Send key up event
|
||||
inputs[1].type = INPUT_KEYBOARD;
|
||||
@@ -298,7 +365,6 @@ void KeypressSimulatorWindowsPlugin::SimulateMediaKey(
|
||||
result->Success(flutter::EncodableValue(true));
|
||||
}
|
||||
|
||||
|
||||
void KeypressSimulatorWindowsPlugin::HandleMethodCall(
|
||||
const flutter::MethodCall<flutter::EncodableValue>& method_call,
|
||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
|
||||
|
||||
@@ -6,8 +6,6 @@ import 'package:bike_control/bluetooth/devices/gamepad/gamepad_device.dart';
|
||||
import 'package:bike_control/bluetooth/devices/gyroscope/gyroscope_steering.dart';
|
||||
import 'package:bike_control/bluetooth/devices/hid/hid_device.dart';
|
||||
import 'package:bike_control/bluetooth/devices/wahoo/wahoo_kickr_headwind.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/ftms_mdns_emulator.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pb.dart';
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/main.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
@@ -17,6 +15,7 @@ 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:prop/prop.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
import 'devices/base_device.dart';
|
||||
@@ -65,6 +64,10 @@ class Connection {
|
||||
lastLogEntries = lastLogEntries.takeLast(kIsWeb ? 1000 : 60).toList();
|
||||
});
|
||||
|
||||
if (!kIsWeb && (Platform.isMacOS || Platform.isWindows)) {
|
||||
core.mediaKeyHandler.initialize();
|
||||
}
|
||||
|
||||
UniversalBle.onAvailabilityChange = (available) {
|
||||
_actionStreams.add(BluetoothAvailabilityNotification(available == AvailabilityState.poweredOn));
|
||||
if (available == AvailabilityState.poweredOn && !kIsWeb) {
|
||||
@@ -91,7 +94,7 @@ class Connection {
|
||||
if (_lastScanResult.none((e) => e.deviceId == result.deviceId && e.services.contentEquals(result.services))) {
|
||||
_lastScanResult.add(result);
|
||||
|
||||
if (kDebugMode) {
|
||||
if (false) {
|
||||
debugPrint('Scan result: ${result.name} - ${result.deviceId}');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bike_control/bluetooth/devices/zwift/constants.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pb.dart' show LogLevel;
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/main.dart';
|
||||
import 'package:bike_control/utils/actions/desktop.dart';
|
||||
@@ -12,19 +11,33 @@ import 'package:bike_control/utils/keymap/manager.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:prop/prop.dart' show LogLevel;
|
||||
|
||||
import '../../utils/keymap/buttons.dart';
|
||||
import '../messages/notification.dart';
|
||||
|
||||
abstract class BaseDevice {
|
||||
final String name;
|
||||
final String? _name;
|
||||
final bool isBeta;
|
||||
final String uniqueId;
|
||||
final List<ControllerButton> availableButtons;
|
||||
|
||||
BaseDevice(this.name, {required this.availableButtons, this.isBeta = false}) {
|
||||
BaseDevice(
|
||||
this._name, {
|
||||
required this.uniqueId,
|
||||
required this.availableButtons,
|
||||
this.isBeta = false,
|
||||
String? buttonPrefix,
|
||||
}) {
|
||||
if (availableButtons.isEmpty && core.actionHandler.supportedApp is CustomApp) {
|
||||
// TODO we should verify where the buttons came from
|
||||
final allButtons = core.actionHandler.supportedApp!.keymap.keyPairs.flatMap((e) => e.buttons);
|
||||
final allButtons = core.actionHandler.supportedApp!.keymap.keyPairs
|
||||
.flatMap((e) => e.buttons)
|
||||
.filter(
|
||||
(e) =>
|
||||
e.sourceDeviceId == uniqueId ||
|
||||
(e.sourceDeviceId == null && buttonPrefix != null && e.name.startsWith(buttonPrefix)),
|
||||
)
|
||||
.toSet();
|
||||
availableButtons.addAll(allButtons);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +47,8 @@ abstract class BaseDevice {
|
||||
Timer? _longPressTimer;
|
||||
Set<ControllerButton> _previouslyPressedButtons = <ControllerButton>{};
|
||||
|
||||
String get name => _name ?? runtimeType.toString();
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
@@ -43,9 +58,7 @@ abstract class BaseDevice {
|
||||
int get hashCode => name.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return name;
|
||||
}
|
||||
String toString() => name;
|
||||
|
||||
final StreamController<BaseNotification> actionStreamInternal = StreamController<BaseNotification>.broadcast();
|
||||
|
||||
@@ -214,7 +227,11 @@ abstract class BaseDevice {
|
||||
// should we display this to the user?
|
||||
KeymapManager().duplicateSync(currentProfile, '$currentProfile (Copy)');
|
||||
}
|
||||
final button = core.actionHandler.supportedApp!.keymap.getOrAddButton(key, creator);
|
||||
var createdButton = creator();
|
||||
if (createdButton.sourceDeviceId == null) {
|
||||
createdButton = createdButton.copyWith(sourceDeviceId: uniqueId);
|
||||
}
|
||||
final button = core.actionHandler.supportedApp!.keymap.getOrAddButton(key, createdButton);
|
||||
|
||||
if (availableButtons.none((e) => e.name == button.name)) {
|
||||
availableButtons.add(button);
|
||||
|
||||
@@ -18,6 +18,7 @@ import 'package:bike_control/main.dart';
|
||||
import 'package:bike_control/pages/device.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/i18n_extension.dart';
|
||||
import 'package:bike_control/utils/keymap/buttons.dart';
|
||||
import 'package:bike_control/widgets/ui/beta_pill.dart';
|
||||
import 'package:bike_control/widgets/ui/device_info.dart';
|
||||
import 'package:bike_control/widgets/ui/loading_widget.dart';
|
||||
@@ -31,12 +32,26 @@ import 'package:universal_ble/universal_ble.dart';
|
||||
import 'cycplus/cycplus_bc2.dart';
|
||||
import 'elite/elite_square.dart';
|
||||
import 'elite/elite_sterzo.dart';
|
||||
import 'thinkrider/thinkrider_vs200.dart';
|
||||
|
||||
abstract class BluetoothDevice extends BaseDevice {
|
||||
final BleDevice scanResult;
|
||||
|
||||
BluetoothDevice(this.scanResult, {required super.availableButtons, super.isBeta = false})
|
||||
: super(scanResult.name ?? 'Unknown Device') {
|
||||
BluetoothDevice(
|
||||
this.scanResult, {
|
||||
required List<ControllerButton> availableButtons,
|
||||
bool allowMultiple = false,
|
||||
bool isBeta = false,
|
||||
String? buttonPrefix,
|
||||
}) : super(
|
||||
scanResult.name,
|
||||
uniqueId: scanResult.deviceId,
|
||||
availableButtons: allowMultiple
|
||||
? availableButtons.map((b) => b.copyWith(sourceDeviceId: scanResult.deviceId)).toList()
|
||||
: availableButtons,
|
||||
isBeta: isBeta,
|
||||
buttonPrefix: buttonPrefix,
|
||||
) {
|
||||
rssi = scanResult.rssi;
|
||||
}
|
||||
|
||||
@@ -55,15 +70,24 @@ abstract class BluetoothDevice extends BaseDevice {
|
||||
ShimanoDi2Constants.SERVICE_UUID,
|
||||
ShimanoDi2Constants.SERVICE_UUID_ALTERNATIVE,
|
||||
OpenBikeControlConstants.SERVICE_UUID,
|
||||
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) {
|
||||
device = switch (scanResult.name) {
|
||||
'Zwift Ride' => ZwiftRide(scanResult),
|
||||
'Zwift Play' => ZwiftPlay(scanResult),
|
||||
'Zwift Play' => ZwiftPlay(scanResult, deviceType: ZwiftDeviceType.playLeft),
|
||||
'Zwift Click' => ZwiftClickV2(scanResult),
|
||||
'SQUARE' => EliteSquare(scanResult),
|
||||
'OpenBike' => OpenBikeControlDevice(scanResult),
|
||||
@@ -74,6 +98,7 @@ abstract class BluetoothDevice extends BaseDevice {
|
||||
_ when scanResult.name!.toUpperCase().startsWith('KICKR BIKE PRO') => WahooKickrBikePro(scanResult),
|
||||
_ when scanResult.name!.toUpperCase().startsWith('CYCPLUS') && scanResult.name!.toUpperCase().contains('BC2') =>
|
||||
CycplusBc2(scanResult),
|
||||
_ when scanResult.name!.toUpperCase().startsWith('THINK VS') => ThinkRiderVs200(scanResult),
|
||||
_ when scanResult.name!.toUpperCase().startsWith('RDR') => ShimanoDi2(scanResult),
|
||||
_ when scanResult.name!.toUpperCase().startsWith('SRAM') => SramAxs(scanResult),
|
||||
_ => null,
|
||||
@@ -83,7 +108,7 @@ abstract class BluetoothDevice extends BaseDevice {
|
||||
null => null,
|
||||
//'Zwift Ride' => ZwiftRide(scanResult), special case for Zwift Ride: we must only connect to the left controller
|
||||
// https://www.makinolo.com/blog/2024/07/26/zwift-ride-protocol/
|
||||
'Zwift Play' => ZwiftPlay(scanResult),
|
||||
//'Zwift Play' => ZwiftPlay(scanResult),
|
||||
//'Zwift Click' => ZwiftClick(scanResult), special case for Zwift Click v2: we must only connect to the left controller
|
||||
_ when scanResult.name!.toUpperCase().startsWith('HEADWIND') => WahooKickrHeadwind(scanResult),
|
||||
_ when scanResult.name!.toUpperCase().startsWith('SQUARE') => EliteSquare(scanResult),
|
||||
@@ -92,7 +117,8 @@ abstract class BluetoothDevice extends BaseDevice {
|
||||
_ when scanResult.name!.toUpperCase().startsWith('KICKR BIKE PRO') => WahooKickrBikePro(scanResult),
|
||||
_ when scanResult.name!.toUpperCase().startsWith('CYCPLUS') && scanResult.name!.toUpperCase().contains('BC2') =>
|
||||
CycplusBc2(scanResult),
|
||||
_ when scanResult.services.contains(CycplusBc2Constants.SERVICE_UUID.toLowerCase()) => CycplusBc2(scanResult),
|
||||
_ when scanResult.name!.toUpperCase().startsWith('THINK VS') => ThinkRiderVs200(scanResult),
|
||||
//_ when scanResult.services.contains(CycplusBc2Constants.SERVICE_UUID.toLowerCase()) => CycplusBc2(scanResult),
|
||||
_ when scanResult.services.contains(ShimanoDi2Constants.SERVICE_UUID.toLowerCase()) => ShimanoDi2(scanResult),
|
||||
_ when scanResult.services.contains(ShimanoDi2Constants.SERVICE_UUID_ALTERNATIVE.toLowerCase()) => ShimanoDi2(
|
||||
scanResult,
|
||||
@@ -126,8 +152,8 @@ abstract class BluetoothDevice extends BaseDevice {
|
||||
final type = ZwiftDeviceType.fromManufacturerData(data.first);
|
||||
device = switch (type) {
|
||||
ZwiftDeviceType.click => ZwiftClick(scanResult),
|
||||
ZwiftDeviceType.playRight => ZwiftPlay(scanResult),
|
||||
ZwiftDeviceType.playLeft => ZwiftPlay(scanResult),
|
||||
ZwiftDeviceType.playRight => ZwiftPlay(scanResult, deviceType: type!),
|
||||
ZwiftDeviceType.playLeft => ZwiftPlay(scanResult, deviceType: type!),
|
||||
ZwiftDeviceType.rideLeft => ZwiftRide(scanResult),
|
||||
//DeviceType.rideRight => ZwiftRide(scanResult), // see comment above
|
||||
ZwiftDeviceType.clickV2Left => ZwiftClickV2(scanResult),
|
||||
@@ -137,16 +163,17 @@ abstract class BluetoothDevice extends BaseDevice {
|
||||
}
|
||||
}
|
||||
|
||||
if (scanResult.name == 'Zwift Ride' && device == null) {
|
||||
if (scanResult.name == 'Zwift Ride' &&
|
||||
device == null &&
|
||||
core.connection.controllerDevices.none((d) => d is ZwiftRide)) {
|
||||
// Fallback for Zwift Ride if nothing else matched => old firmware
|
||||
if (navigatorKey.currentContext?.mounted ?? false) {
|
||||
buildToast(
|
||||
navigatorKey.currentContext!,
|
||||
title: 'Please update your Zwift Ride firmware.',
|
||||
title: 'You may need to update your Zwift Ride firmware.',
|
||||
duration: Duration(seconds: 6),
|
||||
);
|
||||
}
|
||||
device = ZwiftRide(scanResult);
|
||||
}
|
||||
return device;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ class CycplusBc2 extends BluetoothDevice {
|
||||
CycplusBc2(super.scanResult)
|
||||
: super(
|
||||
availableButtons: CycplusBc2Buttons.values,
|
||||
allowMultiple: true,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -40,7 +41,7 @@ class CycplusBc2 extends BluetoothDevice {
|
||||
// Process index 6 (shift up)
|
||||
final currentByte6 = bytes[6];
|
||||
if (_shouldTriggerShift(currentByte6, _lastStateIndex6)) {
|
||||
buttonsToPress.add(CycplusBc2Buttons.shiftUp);
|
||||
buttonsToPress.add(availableButtons[0]);
|
||||
_lastStateIndex6 = 0x00; // Reset after successful press
|
||||
} else {
|
||||
_updateState(currentByte6, (val) => _lastStateIndex6 = val);
|
||||
@@ -49,7 +50,7 @@ class CycplusBc2 extends BluetoothDevice {
|
||||
// Process index 7 (shift down)
|
||||
final currentByte7 = bytes[7];
|
||||
if (_shouldTriggerShift(currentByte7, _lastStateIndex7)) {
|
||||
buttonsToPress.add(CycplusBc2Buttons.shiftDown);
|
||||
buttonsToPress.add(availableButtons[1]);
|
||||
_lastStateIndex7 = 0x00; // Reset after successful press
|
||||
} else {
|
||||
_updateState(currentByte7, (val) => _lastStateIndex7 = val);
|
||||
|
||||
@@ -3,16 +3,18 @@ import 'dart:io';
|
||||
import 'package:bike_control/bluetooth/devices/base_device.dart';
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/pages/device.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/keymap/buttons.dart';
|
||||
import 'package:bike_control/widgets/ui/beta_pill.dart';
|
||||
import 'package:bike_control/widgets/ui/warning.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gamepads/gamepads.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
|
||||
class GamepadDevice extends BaseDevice {
|
||||
final String id;
|
||||
|
||||
GamepadDevice(super.name, {required this.id}) : super(availableButtons: []);
|
||||
GamepadDevice(super.name, {required this.id}) : super(availableButtons: [], uniqueId: id);
|
||||
|
||||
List<ControllerButton> _lastButtonsClicked = [];
|
||||
|
||||
@@ -30,7 +32,7 @@ class GamepadDevice extends BaseDevice {
|
||||
final buttonKey = event.type == KeyType.analog ? '${event.key}_$normalizedValue' : event.key;
|
||||
ControllerButton button = getOrAddButton(
|
||||
buttonKey,
|
||||
() => ControllerButton(buttonKey),
|
||||
() => ControllerButton(buttonKey, sourceDeviceId: id),
|
||||
);
|
||||
|
||||
switch (event.type) {
|
||||
@@ -74,6 +76,14 @@ class GamepadDevice extends BaseDevice {
|
||||
if (isBeta) BetaPill(),
|
||||
],
|
||||
),
|
||||
if (Platform.isAndroid && !core.settings.getLocalEnabled())
|
||||
Warning(
|
||||
children: [
|
||||
Text(
|
||||
'For it to work properly, even when BikeControl is in the background, you need to enable the local connection method in the next tab.',
|
||||
).small,
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:async';
|
||||
|
||||
import 'package:bike_control/bluetooth/devices/base_device.dart';
|
||||
import 'package:bike_control/bluetooth/devices/gyroscope/steering_estimator.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pb.dart';
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/pages/device.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
@@ -11,6 +10,7 @@ import 'package:bike_control/widgets/ui/beta_pill.dart';
|
||||
import 'package:bike_control/widgets/ui/device_info.dart';
|
||||
import 'package:bike_control/widgets/ui/small_progress_indicator.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
import 'package:sensors_plus/sensors_plus.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
|
||||
@@ -22,6 +22,8 @@ class GyroscopeSteering extends BaseDevice {
|
||||
'Phone Steering',
|
||||
availableButtons: GyroscopeSteeringButtons.values,
|
||||
isBeta: true,
|
||||
uniqueId: 'gyroscope_steering_device',
|
||||
buttonPrefix: 'gyro',
|
||||
);
|
||||
|
||||
StreamSubscription<GyroscopeEvent>? _gyroscopeSubscription;
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bike_control/bluetooth/devices/base_device.dart';
|
||||
import 'package:bike_control/utils/actions/android.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:bike_control/widgets/ui/warning.dart';
|
||||
import 'package:flutter/material.dart' show PopupMenuButton, PopupMenuItem;
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
|
||||
class HidDevice extends BaseDevice {
|
||||
HidDevice(super.name) : super(availableButtons: []);
|
||||
HidDevice(super.name) : super(availableButtons: [], uniqueId: name!);
|
||||
|
||||
@override
|
||||
Future<void> connect() {
|
||||
@@ -13,24 +17,36 @@ class HidDevice extends BaseDevice {
|
||||
|
||||
@override
|
||||
Widget showInformation(BuildContext context) {
|
||||
return Row(
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(child: Text(toString())),
|
||||
PopupMenuButton(
|
||||
itemBuilder: (c) => [
|
||||
PopupMenuItem(
|
||||
child: Text('Ignore'),
|
||||
onTap: () {
|
||||
core.connection.disconnect(this, forget: true, persistForget: true);
|
||||
if (core.actionHandler is AndroidActions) {
|
||||
(core.actionHandler as AndroidActions).ignoreHidDevices();
|
||||
} else if (core.mediaKeyHandler.isMediaKeyDetectionEnabled.value) {
|
||||
core.mediaKeyHandler.isMediaKeyDetectionEnabled.value = false;
|
||||
}
|
||||
},
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: Text(toString()).bold),
|
||||
PopupMenuButton(
|
||||
itemBuilder: (c) => [
|
||||
PopupMenuItem(
|
||||
child: Text('Ignore'),
|
||||
onTap: () {
|
||||
core.connection.disconnect(this, forget: true, persistForget: true);
|
||||
if (core.actionHandler is AndroidActions) {
|
||||
(core.actionHandler as AndroidActions).ignoreHidDevices();
|
||||
} else if (core.mediaKeyHandler.isMediaKeyDetectionEnabled.value) {
|
||||
core.mediaKeyHandler.isMediaKeyDetectionEnabled.value = false;
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
if (Platform.isAndroid && !core.settings.getLocalEnabled())
|
||||
Warning(
|
||||
children: [
|
||||
Text(
|
||||
'For it to work properly, even when BikeControl is in the background, you need to enable the local connection method in the next tab.',
|
||||
).small,
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bike_control/bluetooth/devices/trainer_connection.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pb.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/utils/actions/base_actions.dart';
|
||||
|
||||
@@ -4,8 +4,6 @@ import 'package:bike_control/bluetooth/ble.dart';
|
||||
import 'package:bike_control/bluetooth/devices/openbikecontrol/openbikecontrol_device.dart';
|
||||
import 'package:bike_control/bluetooth/devices/openbikecontrol/protocol_parser.dart';
|
||||
import 'package:bike_control/bluetooth/devices/trainer_connection.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/ftms_mdns_emulator.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pbenum.dart';
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart' show AlertNotification, LogNotification;
|
||||
import 'package:bike_control/utils/actions/base_actions.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
@@ -15,6 +13,7 @@ import 'package:bike_control/widgets/title.dart';
|
||||
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
|
||||
class OpenBikeControlBluetoothEmulator extends TrainerConnection {
|
||||
late final _peripheralManager = PeripheralManager();
|
||||
|
||||
@@ -3,8 +3,6 @@ import 'dart:io';
|
||||
import 'package:bike_control/bluetooth/devices/openbikecontrol/openbikecontrol_device.dart';
|
||||
import 'package:bike_control/bluetooth/devices/openbikecontrol/protocol_parser.dart';
|
||||
import 'package:bike_control/bluetooth/devices/trainer_connection.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/ftms_mdns_emulator.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pb.dart';
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/utils/actions/base_actions.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
@@ -13,6 +11,7 @@ import 'package:bike_control/utils/keymap/keymap.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:nsd/nsd.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
|
||||
class OpenBikeControlMdnsEmulator extends TrainerConnection {
|
||||
ServerSocket? _server;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pb.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/widgets/title.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bike_control/bluetooth/devices/zwift/ftms_mdns_emulator.dart';
|
||||
import 'package:bike_control/utils/keymap/buttons.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
|
||||
class ProtocolParseException implements Exception {
|
||||
final String message;
|
||||
@@ -35,8 +35,8 @@ class OpenBikeProtocolParser {
|
||||
0x13: ControllerButton('Right/Look Right', identifier: 0x13, action: InGameAction.navigateRight),
|
||||
0x14: ControllerButton('Select/Confirm', identifier: 0x14, action: InGameAction.select),
|
||||
0x15: ControllerButton('Back/Cancel', identifier: 0x15, action: InGameAction.back),
|
||||
0x16: ControllerButton('Menu', identifier: 0x16),
|
||||
0x17: ControllerButton('Home', identifier: 0x17),
|
||||
0x16: ControllerButton('Menu', identifier: 0x16, action: InGameAction.menu),
|
||||
0x17: ControllerButton('Home', identifier: 0x17, action: InGameAction.home),
|
||||
0x18: ControllerButton('Steer Left', identifier: 0x18, action: InGameAction.steerLeft),
|
||||
0x19: ControllerButton('Steer Right', identifier: 0x19, action: InGameAction.steerRight),
|
||||
// Social/Emotes (0x20-0x2F)
|
||||
|
||||
@@ -10,7 +10,7 @@ import 'package:universal_ble/universal_ble.dart';
|
||||
import '../bluetooth_device.dart';
|
||||
|
||||
class ShimanoDi2 extends BluetoothDevice {
|
||||
ShimanoDi2(super.scanResult) : super(availableButtons: []);
|
||||
ShimanoDi2(super.scanResult) : super(availableButtons: [], buttonPrefix: 'D-Fly Channel ');
|
||||
|
||||
@override
|
||||
Future<void> handleServices(List<BleService> services) async {
|
||||
@@ -42,7 +42,7 @@ class ShimanoDi2 extends BluetoothDevice {
|
||||
|
||||
getOrAddButton(
|
||||
'D-Fly Channel $readableIndex',
|
||||
() => ControllerButton('D-Fly Channel $readableIndex'),
|
||||
() => ControllerButton('D-Fly Channel $readableIndex', sourceDeviceId: device.deviceId),
|
||||
);
|
||||
});
|
||||
_isInitialized = true;
|
||||
@@ -59,7 +59,7 @@ class ShimanoDi2 extends BluetoothDevice {
|
||||
|
||||
final button = getOrAddButton(
|
||||
'D-Fly Channel $readableIndex',
|
||||
() => ControllerButton('D-Fly Channel $readableIndex'),
|
||||
() => ControllerButton('D-Fly Channel $readableIndex', sourceDeviceId: device.deviceId),
|
||||
);
|
||||
if (didChange) {
|
||||
clickedButtons.add(button);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bike_control/bluetooth/devices/zwift/ftms_mdns_emulator.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/keymap/buttons.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
@@ -43,12 +43,12 @@ class SramAxs extends BluetoothDevice {
|
||||
|
||||
ControllerButton _singleClickButton() => getOrAddButton(
|
||||
'SRAM Tap',
|
||||
() => const ControllerButton('SRAM Tap', action: InGameAction.shiftUp),
|
||||
() => ControllerButton('SRAM Tap', action: InGameAction.shiftUp, sourceDeviceId: device.deviceId),
|
||||
);
|
||||
|
||||
ControllerButton _doubleClickButton() => getOrAddButton(
|
||||
'SRAM Double Tap',
|
||||
() => const ControllerButton('SRAM Double Tap', action: InGameAction.shiftDown),
|
||||
() => ControllerButton('SRAM Double Tap', action: InGameAction.shiftDown, sourceDeviceId: device.deviceId),
|
||||
);
|
||||
|
||||
void _emitClick(ControllerButton button) {
|
||||
|
||||
90
lib/bluetooth/devices/thinkrider/thinkrider_vs200.dart
Normal file
90
lib/bluetooth/devices/thinkrider/thinkrider_vs200.dart
Normal file
@@ -0,0 +1,90 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/utils/keymap/buttons.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
import '../bluetooth_device.dart';
|
||||
|
||||
class ThinkRiderVs200 extends BluetoothDevice {
|
||||
ThinkRiderVs200(super.scanResult)
|
||||
: super(
|
||||
availableButtons: ThinkRiderVs200Buttons.values,
|
||||
isBeta: true,
|
||||
allowMultiple: true,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> handleServices(List<BleService> services) async {
|
||||
// Only subscribe to service 0xFEA0
|
||||
final service = services.firstWhere(
|
||||
(e) => e.uuid.toLowerCase() == ThinkRiderVs200Constants.SERVICE_UUID.toLowerCase(),
|
||||
orElse: () => throw Exception('Service not found: ${ThinkRiderVs200Constants.SERVICE_UUID}'),
|
||||
);
|
||||
final characteristic = service.characteristics.firstWhere(
|
||||
(e) => e.uuid.toLowerCase() == ThinkRiderVs200Constants.CHARACTERISTIC_UUID.toLowerCase(),
|
||||
orElse: () => throw Exception('Characteristic not found: ${ThinkRiderVs200Constants.CHARACTERISTIC_UUID}'),
|
||||
);
|
||||
|
||||
await UniversalBle.subscribeNotifications(device.deviceId, service.uuid, characteristic.uuid);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> processCharacteristic(String characteristic, Uint8List bytes) {
|
||||
if (characteristic.toLowerCase() == ThinkRiderVs200Constants.CHARACTERISTIC_UUID.toLowerCase()) {
|
||||
final hexValue = _bytesToHex(bytes).toLowerCase();
|
||||
|
||||
// Log all received values while in beta
|
||||
if (isBeta) {
|
||||
actionStreamInternal.add(LogNotification('VS200 received: $hexValue'));
|
||||
}
|
||||
|
||||
// Check for specific byte patterns
|
||||
if (hexValue == ThinkRiderVs200Constants.SHIFT_UP_PATTERN) {
|
||||
// Plus button pressed
|
||||
actionStreamInternal.add(LogNotification('Shift Up detected: $hexValue'));
|
||||
handleButtonsClickedWithoutLongPressSupport([availableButtons[0]]);
|
||||
} else if (hexValue == ThinkRiderVs200Constants.SHIFT_DOWN_PATTERN) {
|
||||
// Minus button pressed
|
||||
actionStreamInternal.add(LogNotification('Shift Down detected: $hexValue'));
|
||||
handleButtonsClickedWithoutLongPressSupport([availableButtons[1]]);
|
||||
}
|
||||
}
|
||||
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
String _bytesToHex(List<int> bytes) {
|
||||
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
|
||||
}
|
||||
}
|
||||
|
||||
class ThinkRiderVs200Constants {
|
||||
// Service and characteristic UUIDs based on the nRF Connect screenshot
|
||||
static const String SERVICE_UUID = "0000fea0-0000-1000-8000-00805f9b34fb";
|
||||
static const String CHARACTERISTIC_UUID = "0000fea1-0000-1000-8000-00805f9b34fb";
|
||||
|
||||
// Byte patterns for button detection
|
||||
static const String SHIFT_UP_PATTERN = "f3050301fc";
|
||||
static const String SHIFT_DOWN_PATTERN = "f3050300fb";
|
||||
}
|
||||
|
||||
class ThinkRiderVs200Buttons {
|
||||
static const ControllerButton shiftUp = ControllerButton(
|
||||
'shiftUp',
|
||||
action: InGameAction.shiftUp,
|
||||
icon: Icons.add,
|
||||
);
|
||||
|
||||
static const ControllerButton shiftDown = ControllerButton(
|
||||
'shiftDown',
|
||||
action: InGameAction.shiftDown,
|
||||
icon: Icons.remove,
|
||||
);
|
||||
|
||||
static const List<ControllerButton> values = [
|
||||
shiftUp,
|
||||
shiftDown,
|
||||
];
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bike_control/bluetooth/devices/zwift/ftms_mdns_emulator.dart';
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/utils/actions/base_actions.dart';
|
||||
import 'package:bike_control/utils/keymap/buttons.dart';
|
||||
import 'package:bike_control/utils/keymap/keymap.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
import '../bluetooth_device.dart';
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -59,13 +59,13 @@ class ZwiftButtons {
|
||||
// left controller
|
||||
static const ControllerButton navigationUp = ControllerButton(
|
||||
'navigationUp',
|
||||
action: InGameAction.toggleUi,
|
||||
action: InGameAction.up,
|
||||
icon: Icons.keyboard_arrow_up,
|
||||
color: Colors.black,
|
||||
);
|
||||
static const ControllerButton navigationDown = ControllerButton(
|
||||
'navigationDown',
|
||||
action: InGameAction.uturn,
|
||||
action: InGameAction.down,
|
||||
icon: Icons.keyboard_arrow_down,
|
||||
color: Colors.black,
|
||||
);
|
||||
@@ -106,7 +106,7 @@ class ZwiftButtons {
|
||||
action: InGameAction.rideOnBomb,
|
||||
color: Colors.deepOrangeAccent,
|
||||
);
|
||||
static const ControllerButton y = ControllerButton('y', action: null, color: Colors.lightBlue);
|
||||
static const ControllerButton y = ControllerButton('y', action: InGameAction.menu, color: Colors.lightBlue);
|
||||
static const ControllerButton onOffRight = ControllerButton('onOffRight', action: InGameAction.toggleUi);
|
||||
static const ControllerButton sideButtonRight = ControllerButton('sideButtonRight', action: InGameAction.shiftUp);
|
||||
static const ControllerButton paddleRight = ControllerButton('paddleRight', action: InGameAction.shiftUp);
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bike_control/bluetooth/devices/trainer_connection.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/constants.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pbenum.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zwift.pb.dart' show RideKeyPadStatus;
|
||||
import 'package:bike_control/bluetooth/devices/zwift/zwift_ride.dart';
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
@@ -11,17 +6,13 @@ import 'package:bike_control/utils/actions/base_actions.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/keymap/buttons.dart';
|
||||
import 'package:bike_control/utils/keymap/keymap.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:nsd/nsd.dart';
|
||||
import 'package:prop/prop.dart' hide RideButtonMask;
|
||||
|
||||
class FtmsMdnsEmulator extends TrainerConnection {
|
||||
ServerSocket? _tcpServer;
|
||||
Registration? _mdnsRegistration;
|
||||
|
||||
static const String connectionTitle = 'Zwift Network Emulator';
|
||||
|
||||
Socket? _socket;
|
||||
late final ClickEmulator clickEmulator = ClickEmulator();
|
||||
var lastMessageId = 0;
|
||||
|
||||
FtmsMdnsEmulator()
|
||||
@@ -39,298 +30,30 @@ class FtmsMdnsEmulator extends TrainerConnection {
|
||||
InGameAction.back,
|
||||
InGameAction.rideOnBomb,
|
||||
],
|
||||
);
|
||||
|
||||
Future<void> startServer() async {
|
||||
isStarted.value = true;
|
||||
print('Starting mDNS server...');
|
||||
|
||||
// Get local IP
|
||||
final interfaces = await NetworkInterface.list();
|
||||
InternetAddress? localIP;
|
||||
|
||||
for (final interface in interfaces) {
|
||||
for (final addr in interface.addresses) {
|
||||
if (addr.type == InternetAddressType.IPv4 && !addr.isLoopback) {
|
||||
localIP = addr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (localIP != null) break;
|
||||
}
|
||||
|
||||
if (localIP == null) {
|
||||
throw 'Could not find network interface';
|
||||
}
|
||||
|
||||
await _createTcpServer();
|
||||
|
||||
if (kDebugMode) {
|
||||
enableLogging(LogTopic.calls);
|
||||
enableLogging(LogTopic.errors);
|
||||
}
|
||||
disableServiceTypeValidation(true);
|
||||
|
||||
_mdnsRegistration = await register(
|
||||
Service(
|
||||
name: 'KICKR BIKE PRO 1337',
|
||||
addresses: [localIP],
|
||||
port: 36867,
|
||||
type: '_wahoo-fitness-tnp._tcp',
|
||||
txt: {
|
||||
'ble-service-uuids': Uint8List.fromList('FC82'.codeUnits),
|
||||
'mac-address': Uint8List.fromList('50-50-25-6C-66-9C'.codeUnits),
|
||||
'serial-number': Uint8List.fromList('244700181'.codeUnits),
|
||||
},
|
||||
),
|
||||
);
|
||||
print('Server started - advertising service!');
|
||||
}
|
||||
|
||||
void stop() {
|
||||
isStarted.value = false;
|
||||
isConnected.value = false;
|
||||
_tcpServer?.close();
|
||||
if (_mdnsRegistration != null) {
|
||||
unregister(_mdnsRegistration!);
|
||||
}
|
||||
_tcpServer = null;
|
||||
_mdnsRegistration = null;
|
||||
_socket = null;
|
||||
print('Stopped FtmsMdnsEmulator');
|
||||
}
|
||||
|
||||
Future<void> _createTcpServer() async {
|
||||
try {
|
||||
_tcpServer = await ServerSocket.bind(
|
||||
InternetAddress.anyIPv6,
|
||||
36867,
|
||||
shared: true,
|
||||
v6Only: false,
|
||||
);
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('Failed to start server: $e');
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('Server started on port ${_tcpServer!.port}');
|
||||
}
|
||||
|
||||
// Accept connection
|
||||
_tcpServer!.listen(
|
||||
(Socket socket) {
|
||||
_socket = socket;
|
||||
isConnected.value = true;
|
||||
if (kDebugMode) {
|
||||
print('Client connected: ${socket.remoteAddress.address}:${socket.remotePort}');
|
||||
}
|
||||
) {
|
||||
clickEmulator.isStarted.addListener(() {
|
||||
isStarted.value = clickEmulator.isStarted.value;
|
||||
});
|
||||
clickEmulator.isConnected.addListener(() {
|
||||
isConnected.value = clickEmulator.isConnected.value;
|
||||
if (isConnected.value) {
|
||||
core.connection.signalNotification(
|
||||
AlertNotification(LogLevel.LOGLEVEL_INFO, AppLocalizations.current.connected),
|
||||
);
|
||||
|
||||
// Listen for data from the client
|
||||
socket.listen(
|
||||
(List<int> data) {
|
||||
if (kDebugMode) {
|
||||
print('Received message: ${bytesToHex(data)}');
|
||||
}
|
||||
|
||||
final mutable = data.toList();
|
||||
while (mutable.isNotEmpty) {
|
||||
final msgVersion = mutable.takeUInt8();
|
||||
final msgId = mutable.takeUInt8();
|
||||
lastMessageId = msgId;
|
||||
final seqNum = mutable.takeUInt8();
|
||||
final respCode = mutable.takeUInt8(); // Response Code
|
||||
final length = mutable.takeUInt16BE(); // Length of the message body
|
||||
|
||||
final body = mutable.takeBytes(length);
|
||||
if (kDebugMode) {
|
||||
print('Parsed message: ID: $msgId, Body: ${bytesToHex(body)}');
|
||||
}
|
||||
|
||||
Uint8List buildHeader(int responseCode, int bodyLength) {
|
||||
return Uint8List.fromList([
|
||||
msgVersion,
|
||||
msgId,
|
||||
seqNum,
|
||||
responseCode,
|
||||
(bodyLength >> 8) & 0xFF,
|
||||
bodyLength & 0xFF,
|
||||
]);
|
||||
}
|
||||
|
||||
switch (msgId) {
|
||||
case FtmsMdnsConstants.DC_MESSAGE_DISCOVER_SERVICES:
|
||||
final body = hexToBytes(ZwiftConstants.ZWIFT_RIDE_CUSTOM_SERVICE_UUID.toNonDash());
|
||||
|
||||
final header = buildHeader(FtmsMdnsConstants.DC_RC_REQUEST_COMPLETED_SUCCESSFULLY, body.length);
|
||||
final bytes = [...header, ...body];
|
||||
|
||||
// Expected 0101000000100000fc8200001000800000805f9b34fb
|
||||
// Got 0101000000100000fc8200001000800000805f9b34fb
|
||||
_write(socket, bytes);
|
||||
case FtmsMdnsConstants.DC_MESSAGE_DISCOVER_CHARACTERISTICS:
|
||||
final rawUUID = body.takeBytes(16);
|
||||
final serviceUUID = bytesToHex(rawUUID).toUUID();
|
||||
if (serviceUUID == ZwiftConstants.ZWIFT_RIDE_CUSTOM_SERVICE_UUID) {
|
||||
final responseBody = [
|
||||
...rawUUID,
|
||||
...hexToBytes(ZwiftConstants.ZWIFT_SYNC_RX_CHARACTERISTIC_UUID.toNonDash()),
|
||||
...[
|
||||
_propertyVal(['write']),
|
||||
],
|
||||
...hexToBytes(ZwiftConstants.ZWIFT_ASYNC_CHARACTERISTIC_UUID.toNonDash()),
|
||||
...[
|
||||
_propertyVal(['notify']),
|
||||
],
|
||||
...hexToBytes(ZwiftConstants.ZWIFT_SYNC_TX_CHARACTERISTIC_UUID.toNonDash()),
|
||||
...[
|
||||
_propertyVal(['notify']),
|
||||
],
|
||||
];
|
||||
|
||||
final responseData = [
|
||||
...buildHeader(
|
||||
FtmsMdnsConstants.DC_RC_REQUEST_COMPLETED_SUCCESSFULLY,
|
||||
responseBody.length,
|
||||
),
|
||||
...responseBody,
|
||||
];
|
||||
|
||||
// OK: 0102010000430000fc8200001000800000805f9b34fb0000000319ca465186e5fa29dcdd09d1020000000219ca465186e5fa29dcdd09d1040000000419ca465186e5fa29dcdd09d104
|
||||
_write(socket, responseData);
|
||||
}
|
||||
case FtmsMdnsConstants.DC_MESSAGE_READ_CHARACTERISTIC:
|
||||
final rawUUID = body.takeBytes(16);
|
||||
final characteristicUUID = bytesToHex(rawUUID).toUUID();
|
||||
|
||||
print(
|
||||
'Got Read Characteristic UUID: $characteristicUUID',
|
||||
);
|
||||
|
||||
final responseBody = rawUUID;
|
||||
final responseData = [
|
||||
...buildHeader(
|
||||
FtmsMdnsConstants.DC_RC_REQUEST_COMPLETED_SUCCESSFULLY,
|
||||
responseBody.length,
|
||||
),
|
||||
...responseBody,
|
||||
];
|
||||
|
||||
_write(socket, responseData);
|
||||
case FtmsMdnsConstants.DC_MESSAGE_WRITE_CHARACTERISTIC:
|
||||
final rawUUID = body.takeBytes(16);
|
||||
final characteristicUUID = bytesToHex(rawUUID).toUUID();
|
||||
final characteristicData = body.takeBytes(body.length);
|
||||
|
||||
print(
|
||||
'Got Write Characteristic UUID: $characteristicUUID, Data: ${bytesToHex(characteristicData)}',
|
||||
);
|
||||
|
||||
final responseBody = rawUUID;
|
||||
final responseData = [
|
||||
...buildHeader(
|
||||
FtmsMdnsConstants.DC_RC_REQUEST_COMPLETED_SUCCESSFULLY,
|
||||
responseBody.length,
|
||||
),
|
||||
...responseBody,
|
||||
];
|
||||
|
||||
_write(socket, responseData);
|
||||
|
||||
final response = core.zwiftEmulator.handleWriteRequest(
|
||||
characteristicUUID,
|
||||
Uint8List.fromList(characteristicData),
|
||||
);
|
||||
|
||||
if (response != null) {
|
||||
final seqNum = (lastMessageId + 1) % 256;
|
||||
lastMessageId = seqNum;
|
||||
|
||||
final responseBody = [
|
||||
...hexToBytes(ZwiftConstants.ZWIFT_SYNC_TX_CHARACTERISTIC_UUID.toLowerCase().toNonDash()),
|
||||
...response,
|
||||
];
|
||||
final responseData = [
|
||||
// header
|
||||
...Uint8List.fromList([
|
||||
msgVersion,
|
||||
FtmsMdnsConstants.DC_MESSAGE_CHARACTERISTIC_NOTIFICATION,
|
||||
seqNum,
|
||||
FtmsMdnsConstants.DC_RC_REQUEST_COMPLETED_SUCCESSFULLY,
|
||||
(responseBody.length >> 8) & 0xFF,
|
||||
responseBody.length & 0xFF,
|
||||
]),
|
||||
// body
|
||||
...responseBody,
|
||||
];
|
||||
|
||||
// 0106050000180000000419ca465186e5fa29dcdd09d1526964654f6e0203
|
||||
_write(socket, responseData);
|
||||
|
||||
if (response.contentEquals(ZwiftConstants.RIDE_ON)) {
|
||||
_sendKeepAlive();
|
||||
}
|
||||
}
|
||||
return;
|
||||
case FtmsMdnsConstants.DC_MESSAGE_ENABLE_CHARACTERISTIC_NOTIFICATIONS:
|
||||
final rawUUID = body.takeBytes(16);
|
||||
final characteristicUUID = bytesToHex(rawUUID).toUUID();
|
||||
final enabled = body.takeUInt8();
|
||||
print(
|
||||
'Got Enable Notifications for Characteristic UUID: $characteristicUUID, Enabled: $enabled',
|
||||
);
|
||||
|
||||
final responseBody = rawUUID;
|
||||
final responseData = [
|
||||
...buildHeader(
|
||||
FtmsMdnsConstants.DC_RC_REQUEST_COMPLETED_SUCCESSFULLY,
|
||||
responseBody.length,
|
||||
),
|
||||
...responseBody,
|
||||
];
|
||||
|
||||
_write(socket, responseData);
|
||||
case FtmsMdnsConstants.DC_MESSAGE_CHARACTERISTIC_NOTIFICATION:
|
||||
print('Hamlo');
|
||||
default:
|
||||
throw 'DC_ERROR_UNKNOWN_MESSAGE_TYPE';
|
||||
}
|
||||
}
|
||||
},
|
||||
onDone: () {
|
||||
print('Client disconnected: $socket');
|
||||
isConnected.value = false;
|
||||
|
||||
core.connection.signalNotification(
|
||||
AlertNotification(LogLevel.LOGLEVEL_INFO, AppLocalizations.current.disconnected),
|
||||
);
|
||||
_socket = null;
|
||||
},
|
||||
} else {
|
||||
core.connection.signalNotification(
|
||||
AlertNotification(LogLevel.LOGLEVEL_INFO, AppLocalizations.current.disconnected),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _write(Socket socket, List<int> responseData) {
|
||||
if (kDebugMode) {
|
||||
print('Sending response: ${bytesToHex(responseData)}');
|
||||
}
|
||||
socket.add(responseData);
|
||||
Future<void> startServer() async {
|
||||
return clickEmulator.startServer();
|
||||
}
|
||||
|
||||
int _propertyVal(List<String> properties) {
|
||||
int res = 0;
|
||||
|
||||
if (properties.contains('read')) res |= 0x01;
|
||||
if (properties.contains('write')) res |= 0x02;
|
||||
if (properties.contains('indicate')) res |= 0x03;
|
||||
if (properties.contains('notify')) res |= 0x04;
|
||||
|
||||
return res;
|
||||
void stop() {
|
||||
clickEmulator.stop();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -360,125 +83,19 @@ class FtmsMdnsEmulator extends TrainerConnection {
|
||||
|
||||
final bytes = status.writeToBuffer();
|
||||
|
||||
final commandProto = _buildNotify(
|
||||
ZwiftConstants.ZWIFT_ASYNC_CHARACTERISTIC_UUID,
|
||||
Uint8List.fromList([
|
||||
Opcode.CONTROLLER_NOTIFICATION.value,
|
||||
...bytes,
|
||||
]),
|
||||
);
|
||||
|
||||
_write(_socket!, commandProto);
|
||||
clickEmulator.writeNotification(bytes);
|
||||
}
|
||||
|
||||
if (isKeyUp) {
|
||||
final zero = _buildNotify(
|
||||
ZwiftConstants.ZWIFT_ASYNC_CHARACTERISTIC_UUID,
|
||||
Uint8List.fromList([Opcode.CONTROLLER_NOTIFICATION.value, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F]),
|
||||
clickEmulator.writeNotification(
|
||||
Uint8List.fromList([0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F]),
|
||||
);
|
||||
|
||||
_write(_socket!, zero);
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('Sent action $isKeyUp vs $isKeyDown ${keyPair.inGameAction!.title} to Zwift Emulator');
|
||||
print('Sent action up $isKeyUp vs down $isKeyDown ${keyPair.inGameAction!.title} to Zwift Emulator');
|
||||
}
|
||||
return Success('Sent action: ${keyPair.inGameAction!.title}');
|
||||
return Success('Sent action ${isKeyDown ? 'down' : ''} ${isKeyUp ? 'up' : ''}: ${keyPair.inGameAction!.title}');
|
||||
}
|
||||
|
||||
List<int> _buildNotify(String uuid, final List<int> data) {
|
||||
final seqNum = (lastMessageId + 1) % 256;
|
||||
lastMessageId = seqNum;
|
||||
|
||||
final responseBody = [
|
||||
...hexToBytes(uuid.toLowerCase().toNonDash()),
|
||||
...data,
|
||||
];
|
||||
final responseData = [
|
||||
// header
|
||||
...Uint8List.fromList([
|
||||
0x01,
|
||||
FtmsMdnsConstants.DC_MESSAGE_CHARACTERISTIC_NOTIFICATION,
|
||||
seqNum,
|
||||
FtmsMdnsConstants.DC_RC_REQUEST_COMPLETED_SUCCESSFULLY,
|
||||
(responseBody.length >> 8) & 0xFF,
|
||||
responseBody.length & 0xFF,
|
||||
]),
|
||||
// body
|
||||
...responseBody,
|
||||
];
|
||||
return responseData;
|
||||
}
|
||||
|
||||
Future<void> _sendKeepAlive() async {
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
if (_socket != null) {
|
||||
_write(
|
||||
_socket!,
|
||||
_buildNotify(
|
||||
ZwiftConstants.ZWIFT_SYNC_TX_CHARACTERISTIC_UUID,
|
||||
hexToBytes('B70100002041201C00180004001B4F00B701000020798EC5BDEFCBE4563418269E4926FBE1'),
|
||||
),
|
||||
);
|
||||
_sendKeepAlive();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension on String {
|
||||
String toNonDash() {
|
||||
return replaceAll('-', '');
|
||||
}
|
||||
|
||||
String toUUID() {
|
||||
return '${substring(0, 8)}-${substring(8, 12)}-${substring(12, 16)}-${substring(16, 20)}-${substring(20)}';
|
||||
}
|
||||
}
|
||||
|
||||
extension on List<int> {
|
||||
int takeUInt8() {
|
||||
final value = this[0];
|
||||
removeAt(0);
|
||||
return value;
|
||||
}
|
||||
|
||||
int readUInt8(int offset) {
|
||||
return this[offset];
|
||||
}
|
||||
|
||||
int takeUInt16BE() {
|
||||
final value = (this[0] << 8) | this[0 + 1];
|
||||
removeAt(0);
|
||||
removeAt(0);
|
||||
return value;
|
||||
}
|
||||
|
||||
List<int> takeBytes(int length) {
|
||||
final value = sublist(0, length);
|
||||
removeRange(0, length);
|
||||
return value;
|
||||
}
|
||||
|
||||
int readUInt16BE(int i) {
|
||||
final value = (this[i] << 8) | this[i + 1];
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
String bytesToHex(List<int> bytes, {bool spaced = false}) {
|
||||
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(spaced ? ' ' : '');
|
||||
}
|
||||
|
||||
String bytesToReadableHex(List<int> bytes) {
|
||||
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(' ');
|
||||
}
|
||||
|
||||
List<int> hexToBytes(String hex) {
|
||||
final bytes = <int>[];
|
||||
for (var i = 0; i < hex.length; i += 2) {
|
||||
final byte = hex.substring(i, i + 2);
|
||||
bytes.add(int.parse(byte, radix: 16));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
class FtmsMdnsConstants {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,583 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: zp.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
/// ///////////////////////////////////////////////////////////////
|
||||
/// Enumerations
|
||||
/// ///////////////////////////////////////////////////////////////
|
||||
class Opcode extends $pb.ProtobufEnum {
|
||||
static const Opcode GET = Opcode._(0, _omitEnumNames ? '' : 'GET');
|
||||
static const Opcode DEV_INFO_STATUS = Opcode._(1, _omitEnumNames ? '' : 'DEV_INFO_STATUS');
|
||||
static const Opcode BLE_SECURITY_REQUEST = Opcode._(2, _omitEnumNames ? '' : 'BLE_SECURITY_REQUEST');
|
||||
static const Opcode TRAINER_NOTIF = Opcode._(3, _omitEnumNames ? '' : 'TRAINER_NOTIF');
|
||||
static const Opcode TRAINER_CONFIG_SET = Opcode._(4, _omitEnumNames ? '' : 'TRAINER_CONFIG_SET');
|
||||
static const Opcode TRAINER_CONFIG_STATUS = Opcode._(5, _omitEnumNames ? '' : 'TRAINER_CONFIG_STATUS');
|
||||
static const Opcode DEV_INFO_SET = Opcode._(12, _omitEnumNames ? '' : 'DEV_INFO_SET');
|
||||
static const Opcode POWER_OFF = Opcode._(15, _omitEnumNames ? '' : 'POWER_OFF');
|
||||
static const Opcode RESET = Opcode._(24, _omitEnumNames ? '' : 'RESET');
|
||||
static const Opcode BATTERY_NOTIF = Opcode._(25, _omitEnumNames ? '' : 'BATTERY_NOTIF');
|
||||
static const Opcode CONTROLLER_NOTIFICATION = Opcode._(35, _omitEnumNames ? '' : 'CONTROLLER_NOTIFICATION');
|
||||
static const Opcode LOG_DATA = Opcode._(42, _omitEnumNames ? '' : 'LOG_DATA');
|
||||
static const Opcode SPINDOWN_REQUEST = Opcode._(58, _omitEnumNames ? '' : 'SPINDOWN_REQUEST');
|
||||
static const Opcode SPINDOWN_NOTIFICATION = Opcode._(59, _omitEnumNames ? '' : 'SPINDOWN_NOTIFICATION');
|
||||
static const Opcode GET_RESPONSE = Opcode._(60, _omitEnumNames ? '' : 'GET_RESPONSE');
|
||||
static const Opcode STATUS_RESPONSE = Opcode._(62, _omitEnumNames ? '' : 'STATUS_RESPONSE');
|
||||
static const Opcode SET = Opcode._(63, _omitEnumNames ? '' : 'SET');
|
||||
static const Opcode SET_RESPONSE = Opcode._(64, _omitEnumNames ? '' : 'SET_RESPONSE');
|
||||
static const Opcode LOG_LEVEL_SET = Opcode._(65, _omitEnumNames ? '' : 'LOG_LEVEL_SET');
|
||||
static const Opcode DATA_CHANGE_NOTIFICATION = Opcode._(66, _omitEnumNames ? '' : 'DATA_CHANGE_NOTIFICATION');
|
||||
static const Opcode GAME_STATE_NOTIFICATION = Opcode._(67, _omitEnumNames ? '' : 'GAME_STATE_NOTIFICATION');
|
||||
static const Opcode SENSOR_RELAY_CONFIG = Opcode._(68, _omitEnumNames ? '' : 'SENSOR_RELAY_CONFIG');
|
||||
static const Opcode SENSOR_RELAY_GET = Opcode._(69, _omitEnumNames ? '' : 'SENSOR_RELAY_GET');
|
||||
static const Opcode SENSOR_RELAY_RESPONSE = Opcode._(70, _omitEnumNames ? '' : 'SENSOR_RELAY_RESPONSE');
|
||||
static const Opcode SENSOR_RELAY_NOTIFICATION = Opcode._(71, _omitEnumNames ? '' : 'SENSOR_RELAY_NOTIFICATION');
|
||||
static const Opcode HRM_DATA_NOTIFICATION = Opcode._(72, _omitEnumNames ? '' : 'HRM_DATA_NOTIFICATION');
|
||||
static const Opcode WIFI_CONFIG_REQUEST = Opcode._(73, _omitEnumNames ? '' : 'WIFI_CONFIG_REQUEST');
|
||||
static const Opcode WIFI_NOTIFICATION = Opcode._(74, _omitEnumNames ? '' : 'WIFI_NOTIFICATION');
|
||||
static const Opcode POWER_METER_NOTIFICATION = Opcode._(75, _omitEnumNames ? '' : 'POWER_METER_NOTIFICATION');
|
||||
static const Opcode CADENCE_SENSOR_NOTIFICATION = Opcode._(76, _omitEnumNames ? '' : 'CADENCE_SENSOR_NOTIFICATION');
|
||||
static const Opcode DEVICE_UPDATE_REQUEST = Opcode._(77, _omitEnumNames ? '' : 'DEVICE_UPDATE_REQUEST');
|
||||
static const Opcode RELAY_ZP_MESSAGE = Opcode._(78, _omitEnumNames ? '' : 'RELAY_ZP_MESSAGE');
|
||||
static const Opcode RIDE_ON = Opcode._(82, _omitEnumNames ? '' : 'RIDE_ON');
|
||||
static const Opcode RESERVED = Opcode._(253, _omitEnumNames ? '' : 'RESERVED');
|
||||
static const Opcode LOST_CONTROL = Opcode._(254, _omitEnumNames ? '' : 'LOST_CONTROL');
|
||||
static const Opcode VENDOR_MESSAGE = Opcode._(255, _omitEnumNames ? '' : 'VENDOR_MESSAGE');
|
||||
|
||||
static const $core.List<Opcode> values = <Opcode> [
|
||||
GET,
|
||||
DEV_INFO_STATUS,
|
||||
BLE_SECURITY_REQUEST,
|
||||
TRAINER_NOTIF,
|
||||
TRAINER_CONFIG_SET,
|
||||
TRAINER_CONFIG_STATUS,
|
||||
DEV_INFO_SET,
|
||||
POWER_OFF,
|
||||
RESET,
|
||||
BATTERY_NOTIF,
|
||||
CONTROLLER_NOTIFICATION,
|
||||
LOG_DATA,
|
||||
SPINDOWN_REQUEST,
|
||||
SPINDOWN_NOTIFICATION,
|
||||
GET_RESPONSE,
|
||||
STATUS_RESPONSE,
|
||||
SET,
|
||||
SET_RESPONSE,
|
||||
LOG_LEVEL_SET,
|
||||
DATA_CHANGE_NOTIFICATION,
|
||||
GAME_STATE_NOTIFICATION,
|
||||
SENSOR_RELAY_CONFIG,
|
||||
SENSOR_RELAY_GET,
|
||||
SENSOR_RELAY_RESPONSE,
|
||||
SENSOR_RELAY_NOTIFICATION,
|
||||
HRM_DATA_NOTIFICATION,
|
||||
WIFI_CONFIG_REQUEST,
|
||||
WIFI_NOTIFICATION,
|
||||
POWER_METER_NOTIFICATION,
|
||||
CADENCE_SENSOR_NOTIFICATION,
|
||||
DEVICE_UPDATE_REQUEST,
|
||||
RELAY_ZP_MESSAGE,
|
||||
RIDE_ON,
|
||||
RESERVED,
|
||||
LOST_CONTROL,
|
||||
VENDOR_MESSAGE,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, Opcode> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static Opcode? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const Opcode._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
/// Data Objects
|
||||
class DO extends $pb.ProtobufEnum {
|
||||
static const DO PAGE_DEV_INFO = DO._(0, _omitEnumNames ? '' : 'PAGE_DEV_INFO');
|
||||
static const DO PROTOCOL_VERSION = DO._(1, _omitEnumNames ? '' : 'PROTOCOL_VERSION');
|
||||
static const DO SYSTEM_FW_VERSION = DO._(2, _omitEnumNames ? '' : 'SYSTEM_FW_VERSION');
|
||||
static const DO DEVICE_NAME = DO._(3, _omitEnumNames ? '' : 'DEVICE_NAME');
|
||||
static const DO SERIAL_NUMBER = DO._(5, _omitEnumNames ? '' : 'SERIAL_NUMBER');
|
||||
static const DO SYSTEM_HW_REVISION = DO._(6, _omitEnumNames ? '' : 'SYSTEM_HW_REVISION');
|
||||
static const DO DEVICE_CAPABILITIES = DO._(7, _omitEnumNames ? '' : 'DEVICE_CAPABILITIES');
|
||||
static const DO MANUFACTURER_ID = DO._(8, _omitEnumNames ? '' : 'MANUFACTURER_ID');
|
||||
static const DO PRODUCT_ID = DO._(9, _omitEnumNames ? '' : 'PRODUCT_ID');
|
||||
static const DO DEVICE_UID = DO._(10, _omitEnumNames ? '' : 'DEVICE_UID');
|
||||
static const DO PAGE_CLIENT_SERVER_CONFIGURATION = DO._(16, _omitEnumNames ? '' : 'PAGE_CLIENT_SERVER_CONFIGURATION');
|
||||
static const DO CLIENT_SERVER_NOTIFICATIONS = DO._(17, _omitEnumNames ? '' : 'CLIENT_SERVER_NOTIFICATIONS');
|
||||
static const DO PAGE_DEVICE_UPDATE_INFO = DO._(32, _omitEnumNames ? '' : 'PAGE_DEVICE_UPDATE_INFO');
|
||||
static const DO DEVICE_UPDATE_STATUS = DO._(33, _omitEnumNames ? '' : 'DEVICE_UPDATE_STATUS');
|
||||
static const DO DEVICE_UPDATE_NEW_VERSION = DO._(34, _omitEnumNames ? '' : 'DEVICE_UPDATE_NEW_VERSION');
|
||||
static const DO PAGE_DATE_TIME = DO._(48, _omitEnumNames ? '' : 'PAGE_DATE_TIME');
|
||||
static const DO UTC_DATE_TIME = DO._(49, _omitEnumNames ? '' : 'UTC_DATE_TIME');
|
||||
static const DO PAGE_BLE_SECURITY = DO._(64, _omitEnumNames ? '' : 'PAGE_BLE_SECURITY');
|
||||
static const DO BLE_SECURE_CONNECTION_STATUS = DO._(65, _omitEnumNames ? '' : 'BLE_SECURE_CONNECTION_STATUS');
|
||||
static const DO BLE_SECURE_CONNECTION_WINDOW_STATUS = DO._(66, _omitEnumNames ? '' : 'BLE_SECURE_CONNECTION_WINDOW_STATUS');
|
||||
static const DO PAGE_TRAINER_CONFIG = DO._(512, _omitEnumNames ? '' : 'PAGE_TRAINER_CONFIG');
|
||||
static const DO TRAINER_MODE = DO._(513, _omitEnumNames ? '' : 'TRAINER_MODE');
|
||||
static const DO CFG_RESISTANCE = DO._(514, _omitEnumNames ? '' : 'CFG_RESISTANCE');
|
||||
static const DO ERG_POWER = DO._(515, _omitEnumNames ? '' : 'ERG_POWER');
|
||||
static const DO AVERAGING_WINDOW = DO._(516, _omitEnumNames ? '' : 'AVERAGING_WINDOW');
|
||||
static const DO SIM_WIND = DO._(517, _omitEnumNames ? '' : 'SIM_WIND');
|
||||
static const DO SIM_GRADE = DO._(518, _omitEnumNames ? '' : 'SIM_GRADE');
|
||||
static const DO SIM_REAL_GEAR_RATIO = DO._(519, _omitEnumNames ? '' : 'SIM_REAL_GEAR_RATIO');
|
||||
static const DO SIM_VIRT_GEAR_RATIO = DO._(520, _omitEnumNames ? '' : 'SIM_VIRT_GEAR_RATIO');
|
||||
static const DO SIM_CW = DO._(521, _omitEnumNames ? '' : 'SIM_CW');
|
||||
static const DO SIM_WHEEL_DIAMETER = DO._(522, _omitEnumNames ? '' : 'SIM_WHEEL_DIAMETER');
|
||||
static const DO SIM_BIKE_MASS = DO._(523, _omitEnumNames ? '' : 'SIM_BIKE_MASS');
|
||||
static const DO SIM_RIDER_MASS = DO._(524, _omitEnumNames ? '' : 'SIM_RIDER_MASS');
|
||||
static const DO SIM_CRR = DO._(525, _omitEnumNames ? '' : 'SIM_CRR');
|
||||
static const DO SIM_RESERVED_FRONTAL_AREA = DO._(526, _omitEnumNames ? '' : 'SIM_RESERVED_FRONTAL_AREA');
|
||||
static const DO SIM_EBRAKE = DO._(527, _omitEnumNames ? '' : 'SIM_EBRAKE');
|
||||
static const DO PAGE_TRAINER_GEAR_INDEX_CONFIG = DO._(528, _omitEnumNames ? '' : 'PAGE_TRAINER_GEAR_INDEX_CONFIG');
|
||||
static const DO FRONT_GEAR_INDEX = DO._(529, _omitEnumNames ? '' : 'FRONT_GEAR_INDEX');
|
||||
static const DO FRONT_GEAR_INDEX_MAX = DO._(530, _omitEnumNames ? '' : 'FRONT_GEAR_INDEX_MAX');
|
||||
static const DO FRONT_GEAR_INDEX_MIN = DO._(531, _omitEnumNames ? '' : 'FRONT_GEAR_INDEX_MIN');
|
||||
static const DO REAR_GEAR_INDEX = DO._(532, _omitEnumNames ? '' : 'REAR_GEAR_INDEX');
|
||||
static const DO REAR_GEAR_INDEX_MAX = DO._(533, _omitEnumNames ? '' : 'REAR_GEAR_INDEX_MAX');
|
||||
static const DO REAR_GEAR_INDEX_MIN = DO._(534, _omitEnumNames ? '' : 'REAR_GEAR_INDEX_MIN');
|
||||
static const DO PAGE_TRAINER_CONFIG2 = DO._(544, _omitEnumNames ? '' : 'PAGE_TRAINER_CONFIG2');
|
||||
static const DO HIGH_SPEED_DATA = DO._(545, _omitEnumNames ? '' : 'HIGH_SPEED_DATA');
|
||||
static const DO ERG_POWER_SMOOTHING = DO._(546, _omitEnumNames ? '' : 'ERG_POWER_SMOOTHING');
|
||||
static const DO VIRTUAL_SHIFTING_MODE = DO._(547, _omitEnumNames ? '' : 'VIRTUAL_SHIFTING_MODE');
|
||||
static const DO PAGE_DEVICE_TILT_CONFIG = DO._(560, _omitEnumNames ? '' : 'PAGE_DEVICE_TILT_CONFIG');
|
||||
static const DO DEVICE_TILT_ENABLED = DO._(561, _omitEnumNames ? '' : 'DEVICE_TILT_ENABLED');
|
||||
static const DO DEVICE_TILT_GRADIENT_MIN = DO._(562, _omitEnumNames ? '' : 'DEVICE_TILT_GRADIENT_MIN');
|
||||
static const DO DEVICE_TILT_GRADIENT_MAX = DO._(563, _omitEnumNames ? '' : 'DEVICE_TILT_GRADIENT_MAX');
|
||||
static const DO DEVICE_TILT_GRADIENT = DO._(564, _omitEnumNames ? '' : 'DEVICE_TILT_GRADIENT');
|
||||
static const DO BATTERY_STATE = DO._(771, _omitEnumNames ? '' : 'BATTERY_STATE');
|
||||
static const DO PAGE_CONTROLLER_INPUT_CONFIG = DO._(1024, _omitEnumNames ? '' : 'PAGE_CONTROLLER_INPUT_CONFIG');
|
||||
static const DO INPUT_SUPPORTED_DIGITAL_INPUTS = DO._(1025, _omitEnumNames ? '' : 'INPUT_SUPPORTED_DIGITAL_INPUTS');
|
||||
static const DO INPUT_SUPPORTED_ANALOG_INPUTS = DO._(1026, _omitEnumNames ? '' : 'INPUT_SUPPORTED_ANALOG_INPUTS');
|
||||
static const DO INPUT_ANALOG_INPUT_RANGE = DO._(1027, _omitEnumNames ? '' : 'INPUT_ANALOG_INPUT_RANGE');
|
||||
static const DO INPUT_ANALOG_INPUT_DEADZONE = DO._(1028, _omitEnumNames ? '' : 'INPUT_ANALOG_INPUT_DEADZONE');
|
||||
static const DO PAGE_WIFI_CONFIGURATION = DO._(1056, _omitEnumNames ? '' : 'PAGE_WIFI_CONFIGURATION');
|
||||
static const DO WIFI_ENABLED = DO._(1057, _omitEnumNames ? '' : 'WIFI_ENABLED');
|
||||
static const DO WIFI_STATUS = DO._(1058, _omitEnumNames ? '' : 'WIFI_STATUS');
|
||||
static const DO WIFI_SSID = DO._(1059, _omitEnumNames ? '' : 'WIFI_SSID');
|
||||
static const DO WIFI_BAND = DO._(1060, _omitEnumNames ? '' : 'WIFI_BAND');
|
||||
static const DO WIFI_RSSI = DO._(1061, _omitEnumNames ? '' : 'WIFI_RSSI');
|
||||
static const DO WIFI_REGION_CODE = DO._(1062, _omitEnumNames ? '' : 'WIFI_REGION_CODE');
|
||||
static const DO SENSOR_RELAY_DATA_PAGE = DO._(1280, _omitEnumNames ? '' : 'SENSOR_RELAY_DATA_PAGE');
|
||||
static const DO SENSOR_RELAY_SUPPORTED_SENSORS = DO._(1281, _omitEnumNames ? '' : 'SENSOR_RELAY_SUPPORTED_SENSORS');
|
||||
static const DO SENSOR_RELAY_PAIRED_SENSORS = DO._(1282, _omitEnumNames ? '' : 'SENSOR_RELAY_PAIRED_SENSORS');
|
||||
|
||||
static const $core.List<DO> values = <DO> [
|
||||
PAGE_DEV_INFO,
|
||||
PROTOCOL_VERSION,
|
||||
SYSTEM_FW_VERSION,
|
||||
DEVICE_NAME,
|
||||
SERIAL_NUMBER,
|
||||
SYSTEM_HW_REVISION,
|
||||
DEVICE_CAPABILITIES,
|
||||
MANUFACTURER_ID,
|
||||
PRODUCT_ID,
|
||||
DEVICE_UID,
|
||||
PAGE_CLIENT_SERVER_CONFIGURATION,
|
||||
CLIENT_SERVER_NOTIFICATIONS,
|
||||
PAGE_DEVICE_UPDATE_INFO,
|
||||
DEVICE_UPDATE_STATUS,
|
||||
DEVICE_UPDATE_NEW_VERSION,
|
||||
PAGE_DATE_TIME,
|
||||
UTC_DATE_TIME,
|
||||
PAGE_BLE_SECURITY,
|
||||
BLE_SECURE_CONNECTION_STATUS,
|
||||
BLE_SECURE_CONNECTION_WINDOW_STATUS,
|
||||
PAGE_TRAINER_CONFIG,
|
||||
TRAINER_MODE,
|
||||
CFG_RESISTANCE,
|
||||
ERG_POWER,
|
||||
AVERAGING_WINDOW,
|
||||
SIM_WIND,
|
||||
SIM_GRADE,
|
||||
SIM_REAL_GEAR_RATIO,
|
||||
SIM_VIRT_GEAR_RATIO,
|
||||
SIM_CW,
|
||||
SIM_WHEEL_DIAMETER,
|
||||
SIM_BIKE_MASS,
|
||||
SIM_RIDER_MASS,
|
||||
SIM_CRR,
|
||||
SIM_RESERVED_FRONTAL_AREA,
|
||||
SIM_EBRAKE,
|
||||
PAGE_TRAINER_GEAR_INDEX_CONFIG,
|
||||
FRONT_GEAR_INDEX,
|
||||
FRONT_GEAR_INDEX_MAX,
|
||||
FRONT_GEAR_INDEX_MIN,
|
||||
REAR_GEAR_INDEX,
|
||||
REAR_GEAR_INDEX_MAX,
|
||||
REAR_GEAR_INDEX_MIN,
|
||||
PAGE_TRAINER_CONFIG2,
|
||||
HIGH_SPEED_DATA,
|
||||
ERG_POWER_SMOOTHING,
|
||||
VIRTUAL_SHIFTING_MODE,
|
||||
PAGE_DEVICE_TILT_CONFIG,
|
||||
DEVICE_TILT_ENABLED,
|
||||
DEVICE_TILT_GRADIENT_MIN,
|
||||
DEVICE_TILT_GRADIENT_MAX,
|
||||
DEVICE_TILT_GRADIENT,
|
||||
BATTERY_STATE,
|
||||
PAGE_CONTROLLER_INPUT_CONFIG,
|
||||
INPUT_SUPPORTED_DIGITAL_INPUTS,
|
||||
INPUT_SUPPORTED_ANALOG_INPUTS,
|
||||
INPUT_ANALOG_INPUT_RANGE,
|
||||
INPUT_ANALOG_INPUT_DEADZONE,
|
||||
PAGE_WIFI_CONFIGURATION,
|
||||
WIFI_ENABLED,
|
||||
WIFI_STATUS,
|
||||
WIFI_SSID,
|
||||
WIFI_BAND,
|
||||
WIFI_RSSI,
|
||||
WIFI_REGION_CODE,
|
||||
SENSOR_RELAY_DATA_PAGE,
|
||||
SENSOR_RELAY_SUPPORTED_SENSORS,
|
||||
SENSOR_RELAY_PAIRED_SENSORS,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, DO> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static DO? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const DO._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class Status extends $pb.ProtobufEnum {
|
||||
static const Status SUCCESS = Status._(0, _omitEnumNames ? '' : 'SUCCESS');
|
||||
static const Status FAILURE = Status._(1, _omitEnumNames ? '' : 'FAILURE');
|
||||
static const Status BUSY = Status._(2, _omitEnumNames ? '' : 'BUSY');
|
||||
static const Status INVALID_PARAM = Status._(3, _omitEnumNames ? '' : 'INVALID_PARAM');
|
||||
static const Status NOT_PERMITTED = Status._(4, _omitEnumNames ? '' : 'NOT_PERMITTED');
|
||||
static const Status NOT_SUPPORTED = Status._(5, _omitEnumNames ? '' : 'NOT_SUPPORTED');
|
||||
static const Status INVALID_MODE = Status._(6, _omitEnumNames ? '' : 'INVALID_MODE');
|
||||
|
||||
static const $core.List<Status> values = <Status> [
|
||||
SUCCESS,
|
||||
FAILURE,
|
||||
BUSY,
|
||||
INVALID_PARAM,
|
||||
NOT_PERMITTED,
|
||||
NOT_SUPPORTED,
|
||||
INVALID_MODE,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, Status> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static Status? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const Status._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class DeviceType extends $pb.ProtobufEnum {
|
||||
static const DeviceType UNDEFINED = DeviceType._(0, _omitEnumNames ? '' : 'UNDEFINED');
|
||||
static const DeviceType CYCLING_TURBO_TRAINER = DeviceType._(1, _omitEnumNames ? '' : 'CYCLING_TURBO_TRAINER');
|
||||
static const DeviceType USER_INPUT_DEVICE = DeviceType._(2, _omitEnumNames ? '' : 'USER_INPUT_DEVICE');
|
||||
static const DeviceType TREADMILL = DeviceType._(3, _omitEnumNames ? '' : 'TREADMILL');
|
||||
static const DeviceType SENSOR_RELAY = DeviceType._(4, _omitEnumNames ? '' : 'SENSOR_RELAY');
|
||||
static const DeviceType HEART_RATE_MONITOR = DeviceType._(5, _omitEnumNames ? '' : 'HEART_RATE_MONITOR');
|
||||
static const DeviceType POWER_METER = DeviceType._(6, _omitEnumNames ? '' : 'POWER_METER');
|
||||
static const DeviceType CADENCE_SENSOR = DeviceType._(7, _omitEnumNames ? '' : 'CADENCE_SENSOR');
|
||||
static const DeviceType WIFI = DeviceType._(8, _omitEnumNames ? '' : 'WIFI');
|
||||
|
||||
static const $core.List<DeviceType> values = <DeviceType> [
|
||||
UNDEFINED,
|
||||
CYCLING_TURBO_TRAINER,
|
||||
USER_INPUT_DEVICE,
|
||||
TREADMILL,
|
||||
SENSOR_RELAY,
|
||||
HEART_RATE_MONITOR,
|
||||
POWER_METER,
|
||||
CADENCE_SENSOR,
|
||||
WIFI,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, DeviceType> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static DeviceType? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const DeviceType._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class TrainerMode extends $pb.ProtobufEnum {
|
||||
static const TrainerMode MODE_UNKNOWN = TrainerMode._(0, _omitEnumNames ? '' : 'MODE_UNKNOWN');
|
||||
static const TrainerMode MODE_ERG = TrainerMode._(1, _omitEnumNames ? '' : 'MODE_ERG');
|
||||
static const TrainerMode MODE_RESISTANCE = TrainerMode._(2, _omitEnumNames ? '' : 'MODE_RESISTANCE');
|
||||
static const TrainerMode MODE_SIM = TrainerMode._(3, _omitEnumNames ? '' : 'MODE_SIM');
|
||||
|
||||
static const $core.List<TrainerMode> values = <TrainerMode> [
|
||||
MODE_UNKNOWN,
|
||||
MODE_ERG,
|
||||
MODE_RESISTANCE,
|
||||
MODE_SIM,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, TrainerMode> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static TrainerMode? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const TrainerMode._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class ChargingState extends $pb.ProtobufEnum {
|
||||
static const ChargingState CHARGING_IDLE = ChargingState._(0, _omitEnumNames ? '' : 'CHARGING_IDLE');
|
||||
static const ChargingState CHARGING_PROGRESS = ChargingState._(1, _omitEnumNames ? '' : 'CHARGING_PROGRESS');
|
||||
static const ChargingState CHARGING_DONE = ChargingState._(2, _omitEnumNames ? '' : 'CHARGING_DONE');
|
||||
|
||||
static const $core.List<ChargingState> values = <ChargingState> [
|
||||
CHARGING_IDLE,
|
||||
CHARGING_PROGRESS,
|
||||
CHARGING_DONE,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, ChargingState> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static ChargingState? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const ChargingState._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class SpindownStatus extends $pb.ProtobufEnum {
|
||||
static const SpindownStatus SPINDOWN_IDLE = SpindownStatus._(0, _omitEnumNames ? '' : 'SPINDOWN_IDLE');
|
||||
static const SpindownStatus SPINDOWN_REQUESTED = SpindownStatus._(1, _omitEnumNames ? '' : 'SPINDOWN_REQUESTED');
|
||||
static const SpindownStatus SPINDOWN_SUCCESS = SpindownStatus._(2, _omitEnumNames ? '' : 'SPINDOWN_SUCCESS');
|
||||
static const SpindownStatus SPINDOWN_ERROR = SpindownStatus._(3, _omitEnumNames ? '' : 'SPINDOWN_ERROR');
|
||||
static const SpindownStatus SPINDOWN_STOP_PEDALLING = SpindownStatus._(4, _omitEnumNames ? '' : 'SPINDOWN_STOP_PEDALLING');
|
||||
static const SpindownStatus SPINDOWN_ERROR_TIMEOUT = SpindownStatus._(5, _omitEnumNames ? '' : 'SPINDOWN_ERROR_TIMEOUT');
|
||||
static const SpindownStatus SPINDOWN_ERROR_TOSHORT = SpindownStatus._(6, _omitEnumNames ? '' : 'SPINDOWN_ERROR_TOSHORT');
|
||||
static const SpindownStatus SPINDOWN_ERROR_TOSLOW = SpindownStatus._(7, _omitEnumNames ? '' : 'SPINDOWN_ERROR_TOSLOW');
|
||||
static const SpindownStatus SPINDOWN_ERROR_TOFAST = SpindownStatus._(8, _omitEnumNames ? '' : 'SPINDOWN_ERROR_TOFAST');
|
||||
static const SpindownStatus SPINDOWN_ERROR_SAMPLEERROR = SpindownStatus._(9, _omitEnumNames ? '' : 'SPINDOWN_ERROR_SAMPLEERROR');
|
||||
static const SpindownStatus SPINDOWN_ERROR_ABORT = SpindownStatus._(10, _omitEnumNames ? '' : 'SPINDOWN_ERROR_ABORT');
|
||||
|
||||
static const $core.List<SpindownStatus> values = <SpindownStatus> [
|
||||
SPINDOWN_IDLE,
|
||||
SPINDOWN_REQUESTED,
|
||||
SPINDOWN_SUCCESS,
|
||||
SPINDOWN_ERROR,
|
||||
SPINDOWN_STOP_PEDALLING,
|
||||
SPINDOWN_ERROR_TIMEOUT,
|
||||
SPINDOWN_ERROR_TOSHORT,
|
||||
SPINDOWN_ERROR_TOSLOW,
|
||||
SPINDOWN_ERROR_TOFAST,
|
||||
SPINDOWN_ERROR_SAMPLEERROR,
|
||||
SPINDOWN_ERROR_ABORT,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, SpindownStatus> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static SpindownStatus? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const SpindownStatus._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class LogLevel extends $pb.ProtobufEnum {
|
||||
static const LogLevel LOGLEVEL_OFF = LogLevel._(0, _omitEnumNames ? '' : 'LOGLEVEL_OFF');
|
||||
static const LogLevel LOGLEVEL_ERROR = LogLevel._(1, _omitEnumNames ? '' : 'LOGLEVEL_ERROR');
|
||||
static const LogLevel LOGLEVEL_WARNING = LogLevel._(2, _omitEnumNames ? '' : 'LOGLEVEL_WARNING');
|
||||
static const LogLevel LOGLEVEL_INFO = LogLevel._(3, _omitEnumNames ? '' : 'LOGLEVEL_INFO');
|
||||
static const LogLevel LOGLEVEL_DEBUG = LogLevel._(4, _omitEnumNames ? '' : 'LOGLEVEL_DEBUG');
|
||||
static const LogLevel LOGLEVEL_TRACE = LogLevel._(5, _omitEnumNames ? '' : 'LOGLEVEL_TRACE');
|
||||
|
||||
static const $core.List<LogLevel> values = <LogLevel> [
|
||||
LOGLEVEL_OFF,
|
||||
LOGLEVEL_ERROR,
|
||||
LOGLEVEL_WARNING,
|
||||
LOGLEVEL_INFO,
|
||||
LOGLEVEL_DEBUG,
|
||||
LOGLEVEL_TRACE,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, LogLevel> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static LogLevel? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const LogLevel._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class RoadSurfaceType extends $pb.ProtobufEnum {
|
||||
static const RoadSurfaceType ROAD_SURFACE_SMOOTH_TARMAC = RoadSurfaceType._(0, _omitEnumNames ? '' : 'ROAD_SURFACE_SMOOTH_TARMAC');
|
||||
static const RoadSurfaceType ROAD_SURFACE_BRICK_ROAD = RoadSurfaceType._(1, _omitEnumNames ? '' : 'ROAD_SURFACE_BRICK_ROAD');
|
||||
static const RoadSurfaceType ROAD_SURFACE_HARD_COBBLES = RoadSurfaceType._(2, _omitEnumNames ? '' : 'ROAD_SURFACE_HARD_COBBLES');
|
||||
static const RoadSurfaceType ROAD_SURFACE_SOFT_COBBLES = RoadSurfaceType._(3, _omitEnumNames ? '' : 'ROAD_SURFACE_SOFT_COBBLES');
|
||||
static const RoadSurfaceType ROAD_SURFACE_NARROW_WOODEN_PLANKS = RoadSurfaceType._(4, _omitEnumNames ? '' : 'ROAD_SURFACE_NARROW_WOODEN_PLANKS');
|
||||
static const RoadSurfaceType ROAD_SURFACE_WIDE_WOODEN_PLANKS = RoadSurfaceType._(5, _omitEnumNames ? '' : 'ROAD_SURFACE_WIDE_WOODEN_PLANKS');
|
||||
static const RoadSurfaceType ROAD_SURFACE_DIRT = RoadSurfaceType._(6, _omitEnumNames ? '' : 'ROAD_SURFACE_DIRT');
|
||||
static const RoadSurfaceType ROAD_SURFACE_GRAVEL = RoadSurfaceType._(7, _omitEnumNames ? '' : 'ROAD_SURFACE_GRAVEL');
|
||||
static const RoadSurfaceType ROAD_SURFACE_CATTLE_GRID = RoadSurfaceType._(8, _omitEnumNames ? '' : 'ROAD_SURFACE_CATTLE_GRID');
|
||||
static const RoadSurfaceType ROAD_SURFACE_CONCRETE_FLAG_STONES = RoadSurfaceType._(9, _omitEnumNames ? '' : 'ROAD_SURFACE_CONCRETE_FLAG_STONES');
|
||||
static const RoadSurfaceType ROAD_SURFACE_ICE = RoadSurfaceType._(10, _omitEnumNames ? '' : 'ROAD_SURFACE_ICE');
|
||||
|
||||
static const $core.List<RoadSurfaceType> values = <RoadSurfaceType> [
|
||||
ROAD_SURFACE_SMOOTH_TARMAC,
|
||||
ROAD_SURFACE_BRICK_ROAD,
|
||||
ROAD_SURFACE_HARD_COBBLES,
|
||||
ROAD_SURFACE_SOFT_COBBLES,
|
||||
ROAD_SURFACE_NARROW_WOODEN_PLANKS,
|
||||
ROAD_SURFACE_WIDE_WOODEN_PLANKS,
|
||||
ROAD_SURFACE_DIRT,
|
||||
ROAD_SURFACE_GRAVEL,
|
||||
ROAD_SURFACE_CATTLE_GRID,
|
||||
ROAD_SURFACE_CONCRETE_FLAG_STONES,
|
||||
ROAD_SURFACE_ICE,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, RoadSurfaceType> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static RoadSurfaceType? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const RoadSurfaceType._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class WifiStatusCode extends $pb.ProtobufEnum {
|
||||
static const WifiStatusCode WIFI_STATUS_DISABLED = WifiStatusCode._(0, _omitEnumNames ? '' : 'WIFI_STATUS_DISABLED');
|
||||
static const WifiStatusCode WIFI_STATUS_NOT_PROVISIONED = WifiStatusCode._(1, _omitEnumNames ? '' : 'WIFI_STATUS_NOT_PROVISIONED');
|
||||
static const WifiStatusCode WIFI_STATUS_SCANNING = WifiStatusCode._(2, _omitEnumNames ? '' : 'WIFI_STATUS_SCANNING');
|
||||
static const WifiStatusCode WIFI_STATUS_DISCONNECTED = WifiStatusCode._(3, _omitEnumNames ? '' : 'WIFI_STATUS_DISCONNECTED');
|
||||
static const WifiStatusCode WIFI_STATUS_CONNECTING = WifiStatusCode._(4, _omitEnumNames ? '' : 'WIFI_STATUS_CONNECTING');
|
||||
static const WifiStatusCode WIFI_STATUS_CONNECTED = WifiStatusCode._(5, _omitEnumNames ? '' : 'WIFI_STATUS_CONNECTED');
|
||||
static const WifiStatusCode WIFI_STATUS_ERROR = WifiStatusCode._(6, _omitEnumNames ? '' : 'WIFI_STATUS_ERROR');
|
||||
|
||||
static const $core.List<WifiStatusCode> values = <WifiStatusCode> [
|
||||
WIFI_STATUS_DISABLED,
|
||||
WIFI_STATUS_NOT_PROVISIONED,
|
||||
WIFI_STATUS_SCANNING,
|
||||
WIFI_STATUS_DISCONNECTED,
|
||||
WIFI_STATUS_CONNECTING,
|
||||
WIFI_STATUS_CONNECTED,
|
||||
WIFI_STATUS_ERROR,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, WifiStatusCode> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static WifiStatusCode? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const WifiStatusCode._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class WifiErrorCode extends $pb.ProtobufEnum {
|
||||
static const WifiErrorCode WIFI_ERROR_UNKNOWN = WifiErrorCode._(0, _omitEnumNames ? '' : 'WIFI_ERROR_UNKNOWN');
|
||||
static const WifiErrorCode WIFI_ERROR_NO_MEMORY = WifiErrorCode._(1, _omitEnumNames ? '' : 'WIFI_ERROR_NO_MEMORY');
|
||||
static const WifiErrorCode WIFI_ERROR_INVALID_PARAMETERS = WifiErrorCode._(2, _omitEnumNames ? '' : 'WIFI_ERROR_INVALID_PARAMETERS');
|
||||
static const WifiErrorCode WIFI_ERROR_INVALID_STATE = WifiErrorCode._(3, _omitEnumNames ? '' : 'WIFI_ERROR_INVALID_STATE');
|
||||
static const WifiErrorCode WIFI_ERROR_NOT_FOUND = WifiErrorCode._(4, _omitEnumNames ? '' : 'WIFI_ERROR_NOT_FOUND');
|
||||
static const WifiErrorCode WIFI_ERROR_NOT_SUPPORTED = WifiErrorCode._(5, _omitEnumNames ? '' : 'WIFI_ERROR_NOT_SUPPORTED');
|
||||
static const WifiErrorCode WIFI_ERROR_NOT_ALLOWED = WifiErrorCode._(6, _omitEnumNames ? '' : 'WIFI_ERROR_NOT_ALLOWED');
|
||||
static const WifiErrorCode WIFI_ERROR_NOT_INITIALISED = WifiErrorCode._(7, _omitEnumNames ? '' : 'WIFI_ERROR_NOT_INITIALISED');
|
||||
static const WifiErrorCode WIFI_ERROR_NOT_STARTED = WifiErrorCode._(8, _omitEnumNames ? '' : 'WIFI_ERROR_NOT_STARTED');
|
||||
static const WifiErrorCode WIFI_ERROR_TIMEOUT = WifiErrorCode._(9, _omitEnumNames ? '' : 'WIFI_ERROR_TIMEOUT');
|
||||
static const WifiErrorCode WIFI_ERROR_MODE = WifiErrorCode._(10, _omitEnumNames ? '' : 'WIFI_ERROR_MODE');
|
||||
static const WifiErrorCode WIFI_ERROR_SSID_INVALID = WifiErrorCode._(11, _omitEnumNames ? '' : 'WIFI_ERROR_SSID_INVALID');
|
||||
|
||||
static const $core.List<WifiErrorCode> values = <WifiErrorCode> [
|
||||
WIFI_ERROR_UNKNOWN,
|
||||
WIFI_ERROR_NO_MEMORY,
|
||||
WIFI_ERROR_INVALID_PARAMETERS,
|
||||
WIFI_ERROR_INVALID_STATE,
|
||||
WIFI_ERROR_NOT_FOUND,
|
||||
WIFI_ERROR_NOT_SUPPORTED,
|
||||
WIFI_ERROR_NOT_ALLOWED,
|
||||
WIFI_ERROR_NOT_INITIALISED,
|
||||
WIFI_ERROR_NOT_STARTED,
|
||||
WIFI_ERROR_TIMEOUT,
|
||||
WIFI_ERROR_MODE,
|
||||
WIFI_ERROR_SSID_INVALID,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, WifiErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static WifiErrorCode? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const WifiErrorCode._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class InterfaceType extends $pb.ProtobufEnum {
|
||||
static const InterfaceType INTERFACE_BLE = InterfaceType._(1, _omitEnumNames ? '' : 'INTERFACE_BLE');
|
||||
static const InterfaceType INTERFACE_ANT = InterfaceType._(2, _omitEnumNames ? '' : 'INTERFACE_ANT');
|
||||
static const InterfaceType INTERFACE_USB = InterfaceType._(3, _omitEnumNames ? '' : 'INTERFACE_USB');
|
||||
static const InterfaceType INTERFACE_ETH = InterfaceType._(4, _omitEnumNames ? '' : 'INTERFACE_ETH');
|
||||
static const InterfaceType INTERFACE_WIFI = InterfaceType._(5, _omitEnumNames ? '' : 'INTERFACE_WIFI');
|
||||
|
||||
static const $core.List<InterfaceType> values = <InterfaceType> [
|
||||
INTERFACE_BLE,
|
||||
INTERFACE_ANT,
|
||||
INTERFACE_USB,
|
||||
INTERFACE_ETH,
|
||||
INTERFACE_WIFI,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, InterfaceType> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static InterfaceType? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const InterfaceType._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class SensorConnectionStatus extends $pb.ProtobufEnum {
|
||||
static const SensorConnectionStatus SENSOR_STATUS_DISCOVERED = SensorConnectionStatus._(1, _omitEnumNames ? '' : 'SENSOR_STATUS_DISCOVERED');
|
||||
static const SensorConnectionStatus SENSOR_STATUS_DISCONNECTED = SensorConnectionStatus._(2, _omitEnumNames ? '' : 'SENSOR_STATUS_DISCONNECTED');
|
||||
static const SensorConnectionStatus SENSOR_STATUS_PAIRING = SensorConnectionStatus._(3, _omitEnumNames ? '' : 'SENSOR_STATUS_PAIRING');
|
||||
static const SensorConnectionStatus SENSOR_STATUS_CONNECTED = SensorConnectionStatus._(4, _omitEnumNames ? '' : 'SENSOR_STATUS_CONNECTED');
|
||||
|
||||
static const $core.List<SensorConnectionStatus> values = <SensorConnectionStatus> [
|
||||
SENSOR_STATUS_DISCOVERED,
|
||||
SENSOR_STATUS_DISCONNECTED,
|
||||
SENSOR_STATUS_PAIRING,
|
||||
SENSOR_STATUS_CONNECTED,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, SensorConnectionStatus> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static SensorConnectionStatus? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const SensorConnectionStatus._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class BleSecureConnectionStatus extends $pb.ProtobufEnum {
|
||||
static const BleSecureConnectionStatus BLE_CONNECTION_SECURITY_STATUS_NONE = BleSecureConnectionStatus._(0, _omitEnumNames ? '' : 'BLE_CONNECTION_SECURITY_STATUS_NONE');
|
||||
static const BleSecureConnectionStatus BLE_CONNECTION_SECURITY_STATUS_INPROGRESS = BleSecureConnectionStatus._(1, _omitEnumNames ? '' : 'BLE_CONNECTION_SECURITY_STATUS_INPROGRESS');
|
||||
static const BleSecureConnectionStatus BLE_CONNECTION_SECURITY_STATUS_ACTIVE = BleSecureConnectionStatus._(2, _omitEnumNames ? '' : 'BLE_CONNECTION_SECURITY_STATUS_ACTIVE');
|
||||
static const BleSecureConnectionStatus BLE_CONNECTION_SECURITY_STATUS_REJECTED = BleSecureConnectionStatus._(3, _omitEnumNames ? '' : 'BLE_CONNECTION_SECURITY_STATUS_REJECTED');
|
||||
|
||||
static const $core.List<BleSecureConnectionStatus> values = <BleSecureConnectionStatus> [
|
||||
BLE_CONNECTION_SECURITY_STATUS_NONE,
|
||||
BLE_CONNECTION_SECURITY_STATUS_INPROGRESS,
|
||||
BLE_CONNECTION_SECURITY_STATUS_ACTIVE,
|
||||
BLE_CONNECTION_SECURITY_STATUS_REJECTED,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, BleSecureConnectionStatus> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static BleSecureConnectionStatus? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const BleSecureConnectionStatus._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class BleSecureConnectionWindowStatus extends $pb.ProtobufEnum {
|
||||
static const BleSecureConnectionWindowStatus BLE_SECURE_CONNECTION_WINDOW_STATUS_CLOSED = BleSecureConnectionWindowStatus._(0, _omitEnumNames ? '' : 'BLE_SECURE_CONNECTION_WINDOW_STATUS_CLOSED');
|
||||
static const BleSecureConnectionWindowStatus BLE_SECURE_CONNECTION_WINDOW_STATUS_OPEN = BleSecureConnectionWindowStatus._(1, _omitEnumNames ? '' : 'BLE_SECURE_CONNECTION_WINDOW_STATUS_OPEN');
|
||||
|
||||
static const $core.List<BleSecureConnectionWindowStatus> values = <BleSecureConnectionWindowStatus> [
|
||||
BLE_SECURE_CONNECTION_WINDOW_STATUS_CLOSED,
|
||||
BLE_SECURE_CONNECTION_WINDOW_STATUS_OPEN,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, BleSecureConnectionWindowStatus> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static BleSecureConnectionWindowStatus? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const BleSecureConnectionWindowStatus._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class WifiRegionCode_RegionCodeType extends $pb.ProtobufEnum {
|
||||
static const WifiRegionCode_RegionCodeType ALPHA_2 = WifiRegionCode_RegionCodeType._(0, _omitEnumNames ? '' : 'ALPHA_2');
|
||||
static const WifiRegionCode_RegionCodeType ALPHA_3 = WifiRegionCode_RegionCodeType._(1, _omitEnumNames ? '' : 'ALPHA_3');
|
||||
static const WifiRegionCode_RegionCodeType NUMERIC = WifiRegionCode_RegionCodeType._(2, _omitEnumNames ? '' : 'NUMERIC');
|
||||
static const WifiRegionCode_RegionCodeType UNKNOWN = WifiRegionCode_RegionCodeType._(3, _omitEnumNames ? '' : 'UNKNOWN');
|
||||
|
||||
static const $core.List<WifiRegionCode_RegionCodeType> values = <WifiRegionCode_RegionCodeType> [
|
||||
ALPHA_2,
|
||||
ALPHA_3,
|
||||
NUMERIC,
|
||||
UNKNOWN,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, WifiRegionCode_RegionCodeType> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static WifiRegionCode_RegionCodeType? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const WifiRegionCode_RegionCodeType._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
|
||||
const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names');
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: zp.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
export 'zp.pb.dart';
|
||||
|
||||
@@ -1,896 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: zp_vendor.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'zp_vendor.pbenum.dart';
|
||||
|
||||
export 'zp_vendor.pbenum.dart';
|
||||
|
||||
class ControllerSync extends $pb.GeneratedMessage {
|
||||
factory ControllerSync({
|
||||
ControllerSyncStatus? status,
|
||||
$core.int? timeStamp,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (status != null) {
|
||||
$result.status = status;
|
||||
}
|
||||
if (timeStamp != null) {
|
||||
$result.timeStamp = timeStamp;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
ControllerSync._() : super();
|
||||
factory ControllerSync.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory ControllerSync.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ControllerSync', package: const $pb.PackageName(_omitMessageNames ? '' : 'com.zwift.protobuf'), createEmptyInstance: create)
|
||||
..e<ControllerSyncStatus>(1, _omitFieldNames ? '' : 'status', $pb.PbFieldType.OE, defaultOrMaker: ControllerSyncStatus.NOT_CONNECTED, valueOf: ControllerSyncStatus.valueOf, enumValues: ControllerSyncStatus.values)
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'timeStamp', $pb.PbFieldType.O3, protoName: 'timeStamp')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
ControllerSync clone() => ControllerSync()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
ControllerSync copyWith(void Function(ControllerSync) updates) => super.copyWith((message) => updates(message as ControllerSync)) as ControllerSync;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ControllerSync create() => ControllerSync._();
|
||||
ControllerSync createEmptyInstance() => create();
|
||||
static $pb.PbList<ControllerSync> createRepeated() => $pb.PbList<ControllerSync>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ControllerSync getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ControllerSync>(create);
|
||||
static ControllerSync? _defaultInstance;
|
||||
|
||||
/// optional in code; proto3 treats as present when non-zero
|
||||
@$pb.TagNumber(1)
|
||||
ControllerSyncStatus get status => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set status(ControllerSyncStatus v) { setField(1, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasStatus() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearStatus() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.int get timeStamp => $_getIZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set timeStamp($core.int v) { $_setSignedInt32(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasTimeStamp() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearTimeStamp() => clearField(2);
|
||||
}
|
||||
|
||||
class EnableTestMode extends $pb.GeneratedMessage {
|
||||
factory EnableTestMode({
|
||||
$core.bool? enable,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (enable != null) {
|
||||
$result.enable = enable;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
EnableTestMode._() : super();
|
||||
factory EnableTestMode.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory EnableTestMode.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EnableTestMode', package: const $pb.PackageName(_omitMessageNames ? '' : 'com.zwift.protobuf'), createEmptyInstance: create)
|
||||
..aOB(1, _omitFieldNames ? '' : 'enable')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
EnableTestMode clone() => EnableTestMode()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
EnableTestMode copyWith(void Function(EnableTestMode) updates) => super.copyWith((message) => updates(message as EnableTestMode)) as EnableTestMode;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static EnableTestMode create() => EnableTestMode._();
|
||||
EnableTestMode createEmptyInstance() => create();
|
||||
static $pb.PbList<EnableTestMode> createRepeated() => $pb.PbList<EnableTestMode>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static EnableTestMode getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EnableTestMode>(create);
|
||||
static EnableTestMode? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool get enable => $_getBF(0);
|
||||
@$pb.TagNumber(1)
|
||||
set enable($core.bool v) { $_setBool(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasEnable() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearEnable() => clearField(1);
|
||||
}
|
||||
|
||||
class PairDevices extends $pb.GeneratedMessage {
|
||||
factory PairDevices({
|
||||
$core.bool? pair,
|
||||
PairDeviceType? type,
|
||||
$core.List<$core.int>? deviceId,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (pair != null) {
|
||||
$result.pair = pair;
|
||||
}
|
||||
if (type != null) {
|
||||
$result.type = type;
|
||||
}
|
||||
if (deviceId != null) {
|
||||
$result.deviceId = deviceId;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
PairDevices._() : super();
|
||||
factory PairDevices.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory PairDevices.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PairDevices', package: const $pb.PackageName(_omitMessageNames ? '' : 'com.zwift.protobuf'), createEmptyInstance: create)
|
||||
..aOB(1, _omitFieldNames ? '' : 'pair')
|
||||
..e<PairDeviceType>(2, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, defaultOrMaker: PairDeviceType.BLE, valueOf: PairDeviceType.valueOf, enumValues: PairDeviceType.values)
|
||||
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'deviceId', $pb.PbFieldType.OY, protoName: 'deviceId')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
PairDevices clone() => PairDevices()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
PairDevices copyWith(void Function(PairDevices) updates) => super.copyWith((message) => updates(message as PairDevices)) as PairDevices;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PairDevices create() => PairDevices._();
|
||||
PairDevices createEmptyInstance() => create();
|
||||
static $pb.PbList<PairDevices> createRepeated() => $pb.PbList<PairDevices>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PairDevices getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<PairDevices>(create);
|
||||
static PairDevices? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool get pair => $_getBF(0);
|
||||
@$pb.TagNumber(1)
|
||||
set pair($core.bool v) { $_setBool(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasPair() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearPair() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
PairDeviceType get type => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set type(PairDeviceType v) { setField(2, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasType() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearType() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<$core.int> get deviceId => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set deviceId($core.List<$core.int> v) { $_setBytes(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasDeviceId() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearDeviceId() => clearField(3);
|
||||
}
|
||||
|
||||
class DevicePairingDataPage_PairedDevice extends $pb.GeneratedMessage {
|
||||
factory DevicePairingDataPage_PairedDevice({
|
||||
$core.List<$core.int>? device,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (device != null) {
|
||||
$result.device = device;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
DevicePairingDataPage_PairedDevice._() : super();
|
||||
factory DevicePairingDataPage_PairedDevice.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory DevicePairingDataPage_PairedDevice.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'DevicePairingDataPage.PairedDevice', package: const $pb.PackageName(_omitMessageNames ? '' : 'com.zwift.protobuf'), createEmptyInstance: create)
|
||||
..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'device', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
DevicePairingDataPage_PairedDevice clone() => DevicePairingDataPage_PairedDevice()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
DevicePairingDataPage_PairedDevice copyWith(void Function(DevicePairingDataPage_PairedDevice) updates) => super.copyWith((message) => updates(message as DevicePairingDataPage_PairedDevice)) as DevicePairingDataPage_PairedDevice;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DevicePairingDataPage_PairedDevice create() => DevicePairingDataPage_PairedDevice._();
|
||||
DevicePairingDataPage_PairedDevice createEmptyInstance() => create();
|
||||
static $pb.PbList<DevicePairingDataPage_PairedDevice> createRepeated() => $pb.PbList<DevicePairingDataPage_PairedDevice>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DevicePairingDataPage_PairedDevice getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DevicePairingDataPage_PairedDevice>(create);
|
||||
static DevicePairingDataPage_PairedDevice? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<$core.int> get device => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set device($core.List<$core.int> v) { $_setBytes(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasDevice() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearDevice() => clearField(1);
|
||||
}
|
||||
|
||||
class DevicePairingDataPage extends $pb.GeneratedMessage {
|
||||
factory DevicePairingDataPage({
|
||||
$core.int? devicesCount,
|
||||
$core.int? pairingStatus,
|
||||
$core.Iterable<DevicePairingDataPage_PairedDevice>? pairingDevList,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (devicesCount != null) {
|
||||
$result.devicesCount = devicesCount;
|
||||
}
|
||||
if (pairingStatus != null) {
|
||||
$result.pairingStatus = pairingStatus;
|
||||
}
|
||||
if (pairingDevList != null) {
|
||||
$result.pairingDevList.addAll(pairingDevList);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
DevicePairingDataPage._() : super();
|
||||
factory DevicePairingDataPage.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory DevicePairingDataPage.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'DevicePairingDataPage', package: const $pb.PackageName(_omitMessageNames ? '' : 'com.zwift.protobuf'), createEmptyInstance: create)
|
||||
..a<$core.int>(1, _omitFieldNames ? '' : 'devicesCount', $pb.PbFieldType.O3, protoName: 'devicesCount')
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'pairingStatus', $pb.PbFieldType.O3, protoName: 'pairingStatus')
|
||||
..pc<DevicePairingDataPage_PairedDevice>(3, _omitFieldNames ? '' : 'pairingDevList', $pb.PbFieldType.PM, protoName: 'pairingDevList', subBuilder: DevicePairingDataPage_PairedDevice.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
DevicePairingDataPage clone() => DevicePairingDataPage()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
DevicePairingDataPage copyWith(void Function(DevicePairingDataPage) updates) => super.copyWith((message) => updates(message as DevicePairingDataPage)) as DevicePairingDataPage;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DevicePairingDataPage create() => DevicePairingDataPage._();
|
||||
DevicePairingDataPage createEmptyInstance() => create();
|
||||
static $pb.PbList<DevicePairingDataPage> createRepeated() => $pb.PbList<DevicePairingDataPage>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DevicePairingDataPage getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DevicePairingDataPage>(create);
|
||||
static DevicePairingDataPage? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.int get devicesCount => $_getIZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set devicesCount($core.int v) { $_setSignedInt32(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasDevicesCount() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearDevicesCount() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.int get pairingStatus => $_getIZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set pairingStatus($core.int v) { $_setSignedInt32(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasPairingStatus() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearPairingStatus() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<DevicePairingDataPage_PairedDevice> get pairingDevList => $_getList(2);
|
||||
}
|
||||
|
||||
enum SetDfuTest_TestCase {
|
||||
failedEnterDfu,
|
||||
failedStartAdvertising,
|
||||
crcFailure,
|
||||
notSet
|
||||
}
|
||||
|
||||
class SetDfuTest extends $pb.GeneratedMessage {
|
||||
factory SetDfuTest({
|
||||
$core.bool? failedEnterDfu,
|
||||
$core.bool? failedStartAdvertising,
|
||||
$core.int? crcFailure,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (failedEnterDfu != null) {
|
||||
$result.failedEnterDfu = failedEnterDfu;
|
||||
}
|
||||
if (failedStartAdvertising != null) {
|
||||
$result.failedStartAdvertising = failedStartAdvertising;
|
||||
}
|
||||
if (crcFailure != null) {
|
||||
$result.crcFailure = crcFailure;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
SetDfuTest._() : super();
|
||||
factory SetDfuTest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory SetDfuTest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static const $core.Map<$core.int, SetDfuTest_TestCase> _SetDfuTest_TestCaseByTag = {
|
||||
1 : SetDfuTest_TestCase.failedEnterDfu,
|
||||
2 : SetDfuTest_TestCase.failedStartAdvertising,
|
||||
3 : SetDfuTest_TestCase.crcFailure,
|
||||
0 : SetDfuTest_TestCase.notSet
|
||||
};
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SetDfuTest', package: const $pb.PackageName(_omitMessageNames ? '' : 'com.zwift.protobuf'), createEmptyInstance: create)
|
||||
..oo(0, [1, 2, 3])
|
||||
..aOB(1, _omitFieldNames ? '' : 'failedEnterDfu', protoName: 'failedEnterDfu')
|
||||
..aOB(2, _omitFieldNames ? '' : 'failedStartAdvertising', protoName: 'failedStartAdvertising')
|
||||
..a<$core.int>(3, _omitFieldNames ? '' : 'crcFailure', $pb.PbFieldType.O3, protoName: 'crcFailure')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
SetDfuTest clone() => SetDfuTest()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
SetDfuTest copyWith(void Function(SetDfuTest) updates) => super.copyWith((message) => updates(message as SetDfuTest)) as SetDfuTest;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SetDfuTest create() => SetDfuTest._();
|
||||
SetDfuTest createEmptyInstance() => create();
|
||||
static $pb.PbList<SetDfuTest> createRepeated() => $pb.PbList<SetDfuTest>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SetDfuTest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SetDfuTest>(create);
|
||||
static SetDfuTest? _defaultInstance;
|
||||
|
||||
SetDfuTest_TestCase whichTestCase() => _SetDfuTest_TestCaseByTag[$_whichOneof(0)]!;
|
||||
void clearTestCase() => clearField($_whichOneof(0));
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool get failedEnterDfu => $_getBF(0);
|
||||
@$pb.TagNumber(1)
|
||||
set failedEnterDfu($core.bool v) { $_setBool(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasFailedEnterDfu() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearFailedEnterDfu() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool get failedStartAdvertising => $_getBF(1);
|
||||
@$pb.TagNumber(2)
|
||||
set failedStartAdvertising($core.bool v) { $_setBool(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasFailedStartAdvertising() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearFailedStartAdvertising() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.int get crcFailure => $_getIZ(2);
|
||||
@$pb.TagNumber(3)
|
||||
set crcFailure($core.int v) { $_setSignedInt32(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasCrcFailure() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearCrcFailure() => clearField(3);
|
||||
}
|
||||
|
||||
class SetGearTestData extends $pb.GeneratedMessage {
|
||||
factory SetGearTestData({
|
||||
$core.int? frontGearIdx,
|
||||
$core.int? rearGearIdx,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (frontGearIdx != null) {
|
||||
$result.frontGearIdx = frontGearIdx;
|
||||
}
|
||||
if (rearGearIdx != null) {
|
||||
$result.rearGearIdx = rearGearIdx;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
SetGearTestData._() : super();
|
||||
factory SetGearTestData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory SetGearTestData.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SetGearTestData', package: const $pb.PackageName(_omitMessageNames ? '' : 'com.zwift.protobuf'), createEmptyInstance: create)
|
||||
..a<$core.int>(1, _omitFieldNames ? '' : 'frontGearIdx', $pb.PbFieldType.O3, protoName: 'frontGearIdx')
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'rearGearIdx', $pb.PbFieldType.O3, protoName: 'rearGearIdx')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
SetGearTestData clone() => SetGearTestData()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
SetGearTestData copyWith(void Function(SetGearTestData) updates) => super.copyWith((message) => updates(message as SetGearTestData)) as SetGearTestData;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SetGearTestData create() => SetGearTestData._();
|
||||
SetGearTestData createEmptyInstance() => create();
|
||||
static $pb.PbList<SetGearTestData> createRepeated() => $pb.PbList<SetGearTestData>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SetGearTestData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SetGearTestData>(create);
|
||||
static SetGearTestData? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.int get frontGearIdx => $_getIZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set frontGearIdx($core.int v) { $_setSignedInt32(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasFrontGearIdx() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearFrontGearIdx() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.int get rearGearIdx => $_getIZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set rearGearIdx($core.int v) { $_setSignedInt32(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasRearGearIdx() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearRearGearIdx() => clearField(2);
|
||||
}
|
||||
|
||||
class SetHrmTestData extends $pb.GeneratedMessage {
|
||||
factory SetHrmTestData({
|
||||
$core.bool? hrmPresent,
|
||||
$core.int? heartRate,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (hrmPresent != null) {
|
||||
$result.hrmPresent = hrmPresent;
|
||||
}
|
||||
if (heartRate != null) {
|
||||
$result.heartRate = heartRate;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
SetHrmTestData._() : super();
|
||||
factory SetHrmTestData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory SetHrmTestData.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SetHrmTestData', package: const $pb.PackageName(_omitMessageNames ? '' : 'com.zwift.protobuf'), createEmptyInstance: create)
|
||||
..aOB(1, _omitFieldNames ? '' : 'hrmPresent', protoName: 'hrmPresent')
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'heartRate', $pb.PbFieldType.O3, protoName: 'heartRate')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
SetHrmTestData clone() => SetHrmTestData()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
SetHrmTestData copyWith(void Function(SetHrmTestData) updates) => super.copyWith((message) => updates(message as SetHrmTestData)) as SetHrmTestData;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SetHrmTestData create() => SetHrmTestData._();
|
||||
SetHrmTestData createEmptyInstance() => create();
|
||||
static $pb.PbList<SetHrmTestData> createRepeated() => $pb.PbList<SetHrmTestData>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SetHrmTestData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SetHrmTestData>(create);
|
||||
static SetHrmTestData? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool get hrmPresent => $_getBF(0);
|
||||
@$pb.TagNumber(1)
|
||||
set hrmPresent($core.bool v) { $_setBool(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasHrmPresent() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearHrmPresent() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.int get heartRate => $_getIZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set heartRate($core.int v) { $_setSignedInt32(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasHeartRate() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearHeartRate() => clearField(2);
|
||||
}
|
||||
|
||||
class SetInputDeviceTestData_ControllerAnalogEvent extends $pb.GeneratedMessage {
|
||||
factory SetInputDeviceTestData_ControllerAnalogEvent({
|
||||
$core.int? sensorId,
|
||||
$core.int? value,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (sensorId != null) {
|
||||
$result.sensorId = sensorId;
|
||||
}
|
||||
if (value != null) {
|
||||
$result.value = value;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
SetInputDeviceTestData_ControllerAnalogEvent._() : super();
|
||||
factory SetInputDeviceTestData_ControllerAnalogEvent.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory SetInputDeviceTestData_ControllerAnalogEvent.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SetInputDeviceTestData.ControllerAnalogEvent', package: const $pb.PackageName(_omitMessageNames ? '' : 'com.zwift.protobuf'), createEmptyInstance: create)
|
||||
..a<$core.int>(1, _omitFieldNames ? '' : 'sensorId', $pb.PbFieldType.O3, protoName: 'sensorId')
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'value', $pb.PbFieldType.O3)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
SetInputDeviceTestData_ControllerAnalogEvent clone() => SetInputDeviceTestData_ControllerAnalogEvent()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
SetInputDeviceTestData_ControllerAnalogEvent copyWith(void Function(SetInputDeviceTestData_ControllerAnalogEvent) updates) => super.copyWith((message) => updates(message as SetInputDeviceTestData_ControllerAnalogEvent)) as SetInputDeviceTestData_ControllerAnalogEvent;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SetInputDeviceTestData_ControllerAnalogEvent create() => SetInputDeviceTestData_ControllerAnalogEvent._();
|
||||
SetInputDeviceTestData_ControllerAnalogEvent createEmptyInstance() => create();
|
||||
static $pb.PbList<SetInputDeviceTestData_ControllerAnalogEvent> createRepeated() => $pb.PbList<SetInputDeviceTestData_ControllerAnalogEvent>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SetInputDeviceTestData_ControllerAnalogEvent getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SetInputDeviceTestData_ControllerAnalogEvent>(create);
|
||||
static SetInputDeviceTestData_ControllerAnalogEvent? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.int get sensorId => $_getIZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set sensorId($core.int v) { $_setSignedInt32(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasSensorId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearSensorId() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.int get value => $_getIZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set value($core.int v) { $_setSignedInt32(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasValue() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearValue() => clearField(2);
|
||||
}
|
||||
|
||||
class SetInputDeviceTestData extends $pb.GeneratedMessage {
|
||||
factory SetInputDeviceTestData({
|
||||
$core.int? duration,
|
||||
$core.int? buttonEvent,
|
||||
$core.Iterable<SetInputDeviceTestData_ControllerAnalogEvent>? analogEventList,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (duration != null) {
|
||||
$result.duration = duration;
|
||||
}
|
||||
if (buttonEvent != null) {
|
||||
$result.buttonEvent = buttonEvent;
|
||||
}
|
||||
if (analogEventList != null) {
|
||||
$result.analogEventList.addAll(analogEventList);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
SetInputDeviceTestData._() : super();
|
||||
factory SetInputDeviceTestData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory SetInputDeviceTestData.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SetInputDeviceTestData', package: const $pb.PackageName(_omitMessageNames ? '' : 'com.zwift.protobuf'), createEmptyInstance: create)
|
||||
..a<$core.int>(1, _omitFieldNames ? '' : 'duration', $pb.PbFieldType.O3)
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'buttonEvent', $pb.PbFieldType.O3, protoName: 'buttonEvent')
|
||||
..pc<SetInputDeviceTestData_ControllerAnalogEvent>(3, _omitFieldNames ? '' : 'analogEventList', $pb.PbFieldType.PM, protoName: 'analogEventList', subBuilder: SetInputDeviceTestData_ControllerAnalogEvent.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
SetInputDeviceTestData clone() => SetInputDeviceTestData()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
SetInputDeviceTestData copyWith(void Function(SetInputDeviceTestData) updates) => super.copyWith((message) => updates(message as SetInputDeviceTestData)) as SetInputDeviceTestData;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SetInputDeviceTestData create() => SetInputDeviceTestData._();
|
||||
SetInputDeviceTestData createEmptyInstance() => create();
|
||||
static $pb.PbList<SetInputDeviceTestData> createRepeated() => $pb.PbList<SetInputDeviceTestData>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SetInputDeviceTestData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SetInputDeviceTestData>(create);
|
||||
static SetInputDeviceTestData? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.int get duration => $_getIZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set duration($core.int v) { $_setSignedInt32(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasDuration() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearDuration() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.int get buttonEvent => $_getIZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set buttonEvent($core.int v) { $_setSignedInt32(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasButtonEvent() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearButtonEvent() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<SetInputDeviceTestData_ControllerAnalogEvent> get analogEventList => $_getList(2);
|
||||
}
|
||||
|
||||
class SetTrainerTestData extends $pb.GeneratedMessage {
|
||||
factory SetTrainerTestData({
|
||||
$core.int? dataMode,
|
||||
$core.int? interfaces,
|
||||
TestTrainerData? testTrainerData,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (dataMode != null) {
|
||||
$result.dataMode = dataMode;
|
||||
}
|
||||
if (interfaces != null) {
|
||||
$result.interfaces = interfaces;
|
||||
}
|
||||
if (testTrainerData != null) {
|
||||
$result.testTrainerData = testTrainerData;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
SetTrainerTestData._() : super();
|
||||
factory SetTrainerTestData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory SetTrainerTestData.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SetTrainerTestData', package: const $pb.PackageName(_omitMessageNames ? '' : 'com.zwift.protobuf'), createEmptyInstance: create)
|
||||
..a<$core.int>(1, _omitFieldNames ? '' : 'dataMode', $pb.PbFieldType.O3, protoName: 'dataMode')
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'interfaces', $pb.PbFieldType.O3)
|
||||
..aOM<TestTrainerData>(3, _omitFieldNames ? '' : 'testTrainerData', protoName: 'testTrainerData', subBuilder: TestTrainerData.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
SetTrainerTestData clone() => SetTrainerTestData()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
SetTrainerTestData copyWith(void Function(SetTrainerTestData) updates) => super.copyWith((message) => updates(message as SetTrainerTestData)) as SetTrainerTestData;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SetTrainerTestData create() => SetTrainerTestData._();
|
||||
SetTrainerTestData createEmptyInstance() => create();
|
||||
static $pb.PbList<SetTrainerTestData> createRepeated() => $pb.PbList<SetTrainerTestData>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SetTrainerTestData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SetTrainerTestData>(create);
|
||||
static SetTrainerTestData? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.int get dataMode => $_getIZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set dataMode($core.int v) { $_setSignedInt32(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasDataMode() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearDataMode() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.int get interfaces => $_getIZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set interfaces($core.int v) { $_setSignedInt32(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasInterfaces() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearInterfaces() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
TestTrainerData get testTrainerData => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set testTrainerData(TestTrainerData v) { setField(3, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasTestTrainerData() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearTestTrainerData() => clearField(3);
|
||||
@$pb.TagNumber(3)
|
||||
TestTrainerData ensureTestTrainerData() => $_ensure(2);
|
||||
}
|
||||
|
||||
class TestTrainerData extends $pb.GeneratedMessage {
|
||||
factory TestTrainerData({
|
||||
$core.int? power,
|
||||
$core.int? cadence,
|
||||
$core.int? bikeSpeed,
|
||||
$core.int? averagedPower,
|
||||
$core.int? wheelSpeed,
|
||||
$core.int? calculatedRealGearRatio,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (power != null) {
|
||||
$result.power = power;
|
||||
}
|
||||
if (cadence != null) {
|
||||
$result.cadence = cadence;
|
||||
}
|
||||
if (bikeSpeed != null) {
|
||||
$result.bikeSpeed = bikeSpeed;
|
||||
}
|
||||
if (averagedPower != null) {
|
||||
$result.averagedPower = averagedPower;
|
||||
}
|
||||
if (wheelSpeed != null) {
|
||||
$result.wheelSpeed = wheelSpeed;
|
||||
}
|
||||
if (calculatedRealGearRatio != null) {
|
||||
$result.calculatedRealGearRatio = calculatedRealGearRatio;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
TestTrainerData._() : super();
|
||||
factory TestTrainerData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory TestTrainerData.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'TestTrainerData', package: const $pb.PackageName(_omitMessageNames ? '' : 'com.zwift.protobuf'), createEmptyInstance: create)
|
||||
..a<$core.int>(1, _omitFieldNames ? '' : 'power', $pb.PbFieldType.O3)
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'cadence', $pb.PbFieldType.O3)
|
||||
..a<$core.int>(3, _omitFieldNames ? '' : 'bikeSpeed', $pb.PbFieldType.O3, protoName: 'bikeSpeed')
|
||||
..a<$core.int>(4, _omitFieldNames ? '' : 'averagedPower', $pb.PbFieldType.O3, protoName: 'averagedPower')
|
||||
..a<$core.int>(5, _omitFieldNames ? '' : 'wheelSpeed', $pb.PbFieldType.O3, protoName: 'wheelSpeed')
|
||||
..a<$core.int>(6, _omitFieldNames ? '' : 'calculatedRealGearRatio', $pb.PbFieldType.O3, protoName: 'calculatedRealGearRatio')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
TestTrainerData clone() => TestTrainerData()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
TestTrainerData copyWith(void Function(TestTrainerData) updates) => super.copyWith((message) => updates(message as TestTrainerData)) as TestTrainerData;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static TestTrainerData create() => TestTrainerData._();
|
||||
TestTrainerData createEmptyInstance() => create();
|
||||
static $pb.PbList<TestTrainerData> createRepeated() => $pb.PbList<TestTrainerData>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static TestTrainerData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<TestTrainerData>(create);
|
||||
static TestTrainerData? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.int get power => $_getIZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set power($core.int v) { $_setSignedInt32(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasPower() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearPower() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.int get cadence => $_getIZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set cadence($core.int v) { $_setSignedInt32(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasCadence() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearCadence() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.int get bikeSpeed => $_getIZ(2);
|
||||
@$pb.TagNumber(3)
|
||||
set bikeSpeed($core.int v) { $_setSignedInt32(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasBikeSpeed() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearBikeSpeed() => clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.int get averagedPower => $_getIZ(3);
|
||||
@$pb.TagNumber(4)
|
||||
set averagedPower($core.int v) { $_setSignedInt32(3, v); }
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasAveragedPower() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearAveragedPower() => clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.int get wheelSpeed => $_getIZ(4);
|
||||
@$pb.TagNumber(5)
|
||||
set wheelSpeed($core.int v) { $_setSignedInt32(4, v); }
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasWheelSpeed() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearWheelSpeed() => clearField(5);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$core.int get calculatedRealGearRatio => $_getIZ(5);
|
||||
@$pb.TagNumber(6)
|
||||
set calculatedRealGearRatio($core.int v) { $_setSignedInt32(5, v); }
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasCalculatedRealGearRatio() => $_has(5);
|
||||
@$pb.TagNumber(6)
|
||||
void clearCalculatedRealGearRatio() => clearField(6);
|
||||
}
|
||||
|
||||
|
||||
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
|
||||
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
|
||||
@@ -1,101 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: zp_vendor.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
class VendorOpcode extends $pb.ProtobufEnum {
|
||||
static const VendorOpcode UNDEFINED = VendorOpcode._(0, _omitEnumNames ? '' : 'UNDEFINED');
|
||||
static const VendorOpcode CONTROLLER_SYNC = VendorOpcode._(1, _omitEnumNames ? '' : 'CONTROLLER_SYNC');
|
||||
static const VendorOpcode PAIR_DEVICES = VendorOpcode._(2, _omitEnumNames ? '' : 'PAIR_DEVICES');
|
||||
static const VendorOpcode ENABLE_TEST_MODE = VendorOpcode._(65280, _omitEnumNames ? '' : 'ENABLE_TEST_MODE');
|
||||
static const VendorOpcode SET_DFU_TEST = VendorOpcode._(65281, _omitEnumNames ? '' : 'SET_DFU_TEST');
|
||||
static const VendorOpcode SET_TRAINER_TEST_DATA = VendorOpcode._(65282, _omitEnumNames ? '' : 'SET_TRAINER_TEST_DATA');
|
||||
static const VendorOpcode SET_INPUT_DEVICE_TEST_DATA = VendorOpcode._(65283, _omitEnumNames ? '' : 'SET_INPUT_DEVICE_TEST_DATA');
|
||||
static const VendorOpcode SET_GEAR_TEST_DATA = VendorOpcode._(65284, _omitEnumNames ? '' : 'SET_GEAR_TEST_DATA');
|
||||
static const VendorOpcode SET_HRM_TEST_DATA = VendorOpcode._(65285, _omitEnumNames ? '' : 'SET_HRM_TEST_DATA');
|
||||
static const VendorOpcode SET_TEST_DATA = VendorOpcode._(65286, _omitEnumNames ? '' : 'SET_TEST_DATA');
|
||||
|
||||
static const $core.List<VendorOpcode> values = <VendorOpcode> [
|
||||
UNDEFINED,
|
||||
CONTROLLER_SYNC,
|
||||
PAIR_DEVICES,
|
||||
ENABLE_TEST_MODE,
|
||||
SET_DFU_TEST,
|
||||
SET_TRAINER_TEST_DATA,
|
||||
SET_INPUT_DEVICE_TEST_DATA,
|
||||
SET_GEAR_TEST_DATA,
|
||||
SET_HRM_TEST_DATA,
|
||||
SET_TEST_DATA,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, VendorOpcode> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static VendorOpcode? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const VendorOpcode._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class PairDeviceType extends $pb.ProtobufEnum {
|
||||
static const PairDeviceType BLE = PairDeviceType._(0, _omitEnumNames ? '' : 'BLE');
|
||||
static const PairDeviceType ANT = PairDeviceType._(1, _omitEnumNames ? '' : 'ANT');
|
||||
|
||||
static const $core.List<PairDeviceType> values = <PairDeviceType> [
|
||||
BLE,
|
||||
ANT,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, PairDeviceType> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static PairDeviceType? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const PairDeviceType._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
/// Status used by ControllerSync
|
||||
class ControllerSyncStatus extends $pb.ProtobufEnum {
|
||||
static const ControllerSyncStatus NOT_CONNECTED = ControllerSyncStatus._(0, _omitEnumNames ? '' : 'NOT_CONNECTED');
|
||||
static const ControllerSyncStatus CONNECTED = ControllerSyncStatus._(1, _omitEnumNames ? '' : 'CONNECTED');
|
||||
|
||||
static const $core.List<ControllerSyncStatus> values = <ControllerSyncStatus> [
|
||||
NOT_CONNECTED,
|
||||
CONNECTED,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, ControllerSyncStatus> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static ControllerSyncStatus? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const ControllerSyncStatus._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
/// Looks like “data object / page” IDs used with pairing pages
|
||||
class VendorDO extends $pb.ProtobufEnum {
|
||||
static const VendorDO NO_CLUE = VendorDO._(0, _omitEnumNames ? '' : 'NO_CLUE');
|
||||
static const VendorDO PAGE_DEVICE_PAIRING = VendorDO._(61440, _omitEnumNames ? '' : 'PAGE_DEVICE_PAIRING');
|
||||
static const VendorDO DEVICE_COUNT = VendorDO._(61441, _omitEnumNames ? '' : 'DEVICE_COUNT');
|
||||
static const VendorDO PAIRING_STATUS = VendorDO._(61442, _omitEnumNames ? '' : 'PAIRING_STATUS');
|
||||
static const VendorDO PAIRED_DEVICE = VendorDO._(61443, _omitEnumNames ? '' : 'PAIRED_DEVICE');
|
||||
|
||||
static const $core.List<VendorDO> values = <VendorDO> [
|
||||
NO_CLUE,
|
||||
PAGE_DEVICE_PAIRING,
|
||||
DEVICE_COUNT,
|
||||
PAIRING_STATUS,
|
||||
PAIRED_DEVICE,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, VendorDO> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static VendorDO? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const VendorDO._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
|
||||
const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names');
|
||||
@@ -1,267 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: zp_vendor.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:convert' as $convert;
|
||||
import 'dart:core' as $core;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
|
||||
@$core.Deprecated('Use vendorOpcodeDescriptor instead')
|
||||
const VendorOpcode$json = {
|
||||
'1': 'VendorOpcode',
|
||||
'2': [
|
||||
{'1': 'UNDEFINED', '2': 0},
|
||||
{'1': 'CONTROLLER_SYNC', '2': 1},
|
||||
{'1': 'PAIR_DEVICES', '2': 2},
|
||||
{'1': 'ENABLE_TEST_MODE', '2': 65280},
|
||||
{'1': 'SET_DFU_TEST', '2': 65281},
|
||||
{'1': 'SET_TRAINER_TEST_DATA', '2': 65282},
|
||||
{'1': 'SET_INPUT_DEVICE_TEST_DATA', '2': 65283},
|
||||
{'1': 'SET_GEAR_TEST_DATA', '2': 65284},
|
||||
{'1': 'SET_HRM_TEST_DATA', '2': 65285},
|
||||
{'1': 'SET_TEST_DATA', '2': 65286},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `VendorOpcode`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List vendorOpcodeDescriptor = $convert.base64Decode(
|
||||
'CgxWZW5kb3JPcGNvZGUSDQoJVU5ERUZJTkVEEAASEwoPQ09OVFJPTExFUl9TWU5DEAESEAoMUE'
|
||||
'FJUl9ERVZJQ0VTEAISFgoQRU5BQkxFX1RFU1RfTU9ERRCA/gMSEgoMU0VUX0RGVV9URVNUEIH+'
|
||||
'AxIbChVTRVRfVFJBSU5FUl9URVNUX0RBVEEQgv4DEiAKGlNFVF9JTlBVVF9ERVZJQ0VfVEVTVF'
|
||||
'9EQVRBEIP+AxIYChJTRVRfR0VBUl9URVNUX0RBVEEQhP4DEhcKEVNFVF9IUk1fVEVTVF9EQVRB'
|
||||
'EIX+AxITCg1TRVRfVEVTVF9EQVRBEIb+Aw==');
|
||||
|
||||
@$core.Deprecated('Use pairDeviceTypeDescriptor instead')
|
||||
const PairDeviceType$json = {
|
||||
'1': 'PairDeviceType',
|
||||
'2': [
|
||||
{'1': 'BLE', '2': 0},
|
||||
{'1': 'ANT', '2': 1},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `PairDeviceType`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List pairDeviceTypeDescriptor = $convert.base64Decode(
|
||||
'Cg5QYWlyRGV2aWNlVHlwZRIHCgNCTEUQABIHCgNBTlQQAQ==');
|
||||
|
||||
@$core.Deprecated('Use controllerSyncStatusDescriptor instead')
|
||||
const ControllerSyncStatus$json = {
|
||||
'1': 'ControllerSyncStatus',
|
||||
'2': [
|
||||
{'1': 'NOT_CONNECTED', '2': 0},
|
||||
{'1': 'CONNECTED', '2': 1},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ControllerSyncStatus`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List controllerSyncStatusDescriptor = $convert.base64Decode(
|
||||
'ChRDb250cm9sbGVyU3luY1N0YXR1cxIRCg1OT1RfQ09OTkVDVEVEEAASDQoJQ09OTkVDVEVEEA'
|
||||
'E=');
|
||||
|
||||
@$core.Deprecated('Use vendorDODescriptor instead')
|
||||
const VendorDO$json = {
|
||||
'1': 'VendorDO',
|
||||
'2': [
|
||||
{'1': 'NO_CLUE', '2': 0},
|
||||
{'1': 'PAGE_DEVICE_PAIRING', '2': 61440},
|
||||
{'1': 'DEVICE_COUNT', '2': 61441},
|
||||
{'1': 'PAIRING_STATUS', '2': 61442},
|
||||
{'1': 'PAIRED_DEVICE', '2': 61443},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `VendorDO`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List vendorDODescriptor = $convert.base64Decode(
|
||||
'CghWZW5kb3JETxILCgdOT19DTFVFEAASGQoTUEFHRV9ERVZJQ0VfUEFJUklORxCA4AMSEgoMRE'
|
||||
'VWSUNFX0NPVU5UEIHgAxIUCg5QQUlSSU5HX1NUQVRVUxCC4AMSEwoNUEFJUkVEX0RFVklDRRCD'
|
||||
'4AM=');
|
||||
|
||||
@$core.Deprecated('Use controllerSyncDescriptor instead')
|
||||
const ControllerSync$json = {
|
||||
'1': 'ControllerSync',
|
||||
'2': [
|
||||
{'1': 'status', '3': 1, '4': 1, '5': 14, '6': '.com.zwift.protobuf.ControllerSyncStatus', '10': 'status'},
|
||||
{'1': 'timeStamp', '3': 2, '4': 1, '5': 5, '10': 'timeStamp'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ControllerSync`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List controllerSyncDescriptor = $convert.base64Decode(
|
||||
'Cg5Db250cm9sbGVyU3luYxJACgZzdGF0dXMYASABKA4yKC5jb20uendpZnQucHJvdG9idWYuQ2'
|
||||
'9udHJvbGxlclN5bmNTdGF0dXNSBnN0YXR1cxIcCgl0aW1lU3RhbXAYAiABKAVSCXRpbWVTdGFt'
|
||||
'cA==');
|
||||
|
||||
@$core.Deprecated('Use enableTestModeDescriptor instead')
|
||||
const EnableTestMode$json = {
|
||||
'1': 'EnableTestMode',
|
||||
'2': [
|
||||
{'1': 'enable', '3': 1, '4': 1, '5': 8, '10': 'enable'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `EnableTestMode`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List enableTestModeDescriptor = $convert.base64Decode(
|
||||
'Cg5FbmFibGVUZXN0TW9kZRIWCgZlbmFibGUYASABKAhSBmVuYWJsZQ==');
|
||||
|
||||
@$core.Deprecated('Use pairDevicesDescriptor instead')
|
||||
const PairDevices$json = {
|
||||
'1': 'PairDevices',
|
||||
'2': [
|
||||
{'1': 'pair', '3': 1, '4': 1, '5': 8, '10': 'pair'},
|
||||
{'1': 'type', '3': 2, '4': 1, '5': 14, '6': '.com.zwift.protobuf.PairDeviceType', '10': 'type'},
|
||||
{'1': 'deviceId', '3': 3, '4': 1, '5': 12, '10': 'deviceId'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `PairDevices`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List pairDevicesDescriptor = $convert.base64Decode(
|
||||
'CgtQYWlyRGV2aWNlcxISCgRwYWlyGAEgASgIUgRwYWlyEjYKBHR5cGUYAiABKA4yIi5jb20uen'
|
||||
'dpZnQucHJvdG9idWYuUGFpckRldmljZVR5cGVSBHR5cGUSGgoIZGV2aWNlSWQYAyABKAxSCGRl'
|
||||
'dmljZUlk');
|
||||
|
||||
@$core.Deprecated('Use devicePairingDataPageDescriptor instead')
|
||||
const DevicePairingDataPage$json = {
|
||||
'1': 'DevicePairingDataPage',
|
||||
'2': [
|
||||
{'1': 'devicesCount', '3': 1, '4': 1, '5': 5, '10': 'devicesCount'},
|
||||
{'1': 'pairingStatus', '3': 2, '4': 1, '5': 5, '10': 'pairingStatus'},
|
||||
{'1': 'pairingDevList', '3': 3, '4': 3, '5': 11, '6': '.com.zwift.protobuf.DevicePairingDataPage.PairedDevice', '10': 'pairingDevList'},
|
||||
],
|
||||
'3': [DevicePairingDataPage_PairedDevice$json],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use devicePairingDataPageDescriptor instead')
|
||||
const DevicePairingDataPage_PairedDevice$json = {
|
||||
'1': 'PairedDevice',
|
||||
'2': [
|
||||
{'1': 'device', '3': 1, '4': 1, '5': 12, '10': 'device'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `DevicePairingDataPage`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List devicePairingDataPageDescriptor = $convert.base64Decode(
|
||||
'ChVEZXZpY2VQYWlyaW5nRGF0YVBhZ2USIgoMZGV2aWNlc0NvdW50GAEgASgFUgxkZXZpY2VzQ2'
|
||||
'91bnQSJAoNcGFpcmluZ1N0YXR1cxgCIAEoBVINcGFpcmluZ1N0YXR1cxJeCg5wYWlyaW5nRGV2'
|
||||
'TGlzdBgDIAMoCzI2LmNvbS56d2lmdC5wcm90b2J1Zi5EZXZpY2VQYWlyaW5nRGF0YVBhZ2UuUG'
|
||||
'FpcmVkRGV2aWNlUg5wYWlyaW5nRGV2TGlzdBomCgxQYWlyZWREZXZpY2USFgoGZGV2aWNlGAEg'
|
||||
'ASgMUgZkZXZpY2U=');
|
||||
|
||||
@$core.Deprecated('Use setDfuTestDescriptor instead')
|
||||
const SetDfuTest$json = {
|
||||
'1': 'SetDfuTest',
|
||||
'2': [
|
||||
{'1': 'failedEnterDfu', '3': 1, '4': 1, '5': 8, '9': 0, '10': 'failedEnterDfu'},
|
||||
{'1': 'failedStartAdvertising', '3': 2, '4': 1, '5': 8, '9': 0, '10': 'failedStartAdvertising'},
|
||||
{'1': 'crcFailure', '3': 3, '4': 1, '5': 5, '9': 0, '10': 'crcFailure'},
|
||||
],
|
||||
'8': [
|
||||
{'1': 'test_case'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `SetDfuTest`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List setDfuTestDescriptor = $convert.base64Decode(
|
||||
'CgpTZXREZnVUZXN0EigKDmZhaWxlZEVudGVyRGZ1GAEgASgISABSDmZhaWxlZEVudGVyRGZ1Ej'
|
||||
'gKFmZhaWxlZFN0YXJ0QWR2ZXJ0aXNpbmcYAiABKAhIAFIWZmFpbGVkU3RhcnRBZHZlcnRpc2lu'
|
||||
'ZxIgCgpjcmNGYWlsdXJlGAMgASgFSABSCmNyY0ZhaWx1cmVCCwoJdGVzdF9jYXNl');
|
||||
|
||||
@$core.Deprecated('Use setGearTestDataDescriptor instead')
|
||||
const SetGearTestData$json = {
|
||||
'1': 'SetGearTestData',
|
||||
'2': [
|
||||
{'1': 'frontGearIdx', '3': 1, '4': 1, '5': 5, '10': 'frontGearIdx'},
|
||||
{'1': 'rearGearIdx', '3': 2, '4': 1, '5': 5, '10': 'rearGearIdx'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `SetGearTestData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List setGearTestDataDescriptor = $convert.base64Decode(
|
||||
'Cg9TZXRHZWFyVGVzdERhdGESIgoMZnJvbnRHZWFySWR4GAEgASgFUgxmcm9udEdlYXJJZHgSIA'
|
||||
'oLcmVhckdlYXJJZHgYAiABKAVSC3JlYXJHZWFySWR4');
|
||||
|
||||
@$core.Deprecated('Use setHrmTestDataDescriptor instead')
|
||||
const SetHrmTestData$json = {
|
||||
'1': 'SetHrmTestData',
|
||||
'2': [
|
||||
{'1': 'hrmPresent', '3': 1, '4': 1, '5': 8, '10': 'hrmPresent'},
|
||||
{'1': 'heartRate', '3': 2, '4': 1, '5': 5, '10': 'heartRate'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `SetHrmTestData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List setHrmTestDataDescriptor = $convert.base64Decode(
|
||||
'Cg5TZXRIcm1UZXN0RGF0YRIeCgpocm1QcmVzZW50GAEgASgIUgpocm1QcmVzZW50EhwKCWhlYX'
|
||||
'J0UmF0ZRgCIAEoBVIJaGVhcnRSYXRl');
|
||||
|
||||
@$core.Deprecated('Use setInputDeviceTestDataDescriptor instead')
|
||||
const SetInputDeviceTestData$json = {
|
||||
'1': 'SetInputDeviceTestData',
|
||||
'2': [
|
||||
{'1': 'duration', '3': 1, '4': 1, '5': 5, '10': 'duration'},
|
||||
{'1': 'buttonEvent', '3': 2, '4': 1, '5': 5, '10': 'buttonEvent'},
|
||||
{'1': 'analogEventList', '3': 3, '4': 3, '5': 11, '6': '.com.zwift.protobuf.SetInputDeviceTestData.ControllerAnalogEvent', '10': 'analogEventList'},
|
||||
],
|
||||
'3': [SetInputDeviceTestData_ControllerAnalogEvent$json],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use setInputDeviceTestDataDescriptor instead')
|
||||
const SetInputDeviceTestData_ControllerAnalogEvent$json = {
|
||||
'1': 'ControllerAnalogEvent',
|
||||
'2': [
|
||||
{'1': 'sensorId', '3': 1, '4': 1, '5': 5, '10': 'sensorId'},
|
||||
{'1': 'value', '3': 2, '4': 1, '5': 5, '10': 'value'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `SetInputDeviceTestData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List setInputDeviceTestDataDescriptor = $convert.base64Decode(
|
||||
'ChZTZXRJbnB1dERldmljZVRlc3REYXRhEhoKCGR1cmF0aW9uGAEgASgFUghkdXJhdGlvbhIgCg'
|
||||
'tidXR0b25FdmVudBgCIAEoBVILYnV0dG9uRXZlbnQSagoPYW5hbG9nRXZlbnRMaXN0GAMgAygL'
|
||||
'MkAuY29tLnp3aWZ0LnByb3RvYnVmLlNldElucHV0RGV2aWNlVGVzdERhdGEuQ29udHJvbGxlck'
|
||||
'FuYWxvZ0V2ZW50Ug9hbmFsb2dFdmVudExpc3QaSQoVQ29udHJvbGxlckFuYWxvZ0V2ZW50EhoK'
|
||||
'CHNlbnNvcklkGAEgASgFUghzZW5zb3JJZBIUCgV2YWx1ZRgCIAEoBVIFdmFsdWU=');
|
||||
|
||||
@$core.Deprecated('Use setTrainerTestDataDescriptor instead')
|
||||
const SetTrainerTestData$json = {
|
||||
'1': 'SetTrainerTestData',
|
||||
'2': [
|
||||
{'1': 'dataMode', '3': 1, '4': 1, '5': 5, '10': 'dataMode'},
|
||||
{'1': 'interfaces', '3': 2, '4': 1, '5': 5, '10': 'interfaces'},
|
||||
{'1': 'testTrainerData', '3': 3, '4': 1, '5': 11, '6': '.com.zwift.protobuf.TestTrainerData', '10': 'testTrainerData'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `SetTrainerTestData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List setTrainerTestDataDescriptor = $convert.base64Decode(
|
||||
'ChJTZXRUcmFpbmVyVGVzdERhdGESGgoIZGF0YU1vZGUYASABKAVSCGRhdGFNb2RlEh4KCmludG'
|
||||
'VyZmFjZXMYAiABKAVSCmludGVyZmFjZXMSTQoPdGVzdFRyYWluZXJEYXRhGAMgASgLMiMuY29t'
|
||||
'Lnp3aWZ0LnByb3RvYnVmLlRlc3RUcmFpbmVyRGF0YVIPdGVzdFRyYWluZXJEYXRh');
|
||||
|
||||
@$core.Deprecated('Use testTrainerDataDescriptor instead')
|
||||
const TestTrainerData$json = {
|
||||
'1': 'TestTrainerData',
|
||||
'2': [
|
||||
{'1': 'power', '3': 1, '4': 1, '5': 5, '10': 'power'},
|
||||
{'1': 'cadence', '3': 2, '4': 1, '5': 5, '10': 'cadence'},
|
||||
{'1': 'bikeSpeed', '3': 3, '4': 1, '5': 5, '10': 'bikeSpeed'},
|
||||
{'1': 'averagedPower', '3': 4, '4': 1, '5': 5, '10': 'averagedPower'},
|
||||
{'1': 'wheelSpeed', '3': 5, '4': 1, '5': 5, '10': 'wheelSpeed'},
|
||||
{'1': 'calculatedRealGearRatio', '3': 6, '4': 1, '5': 5, '10': 'calculatedRealGearRatio'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `TestTrainerData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List testTrainerDataDescriptor = $convert.base64Decode(
|
||||
'Cg9UZXN0VHJhaW5lckRhdGESFAoFcG93ZXIYASABKAVSBXBvd2VyEhgKB2NhZGVuY2UYAiABKA'
|
||||
'VSB2NhZGVuY2USHAoJYmlrZVNwZWVkGAMgASgFUgliaWtlU3BlZWQSJAoNYXZlcmFnZWRQb3dl'
|
||||
'chgEIAEoBVINYXZlcmFnZWRQb3dlchIeCgp3aGVlbFNwZWVkGAUgASgFUgp3aGVlbFNwZWVkEj'
|
||||
'gKF2NhbGN1bGF0ZWRSZWFsR2VhclJhdGlvGAYgASgFUhdjYWxjdWxhdGVkUmVhbEdlYXJSYXRp'
|
||||
'bw==');
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: zp_vendor.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
export 'zp_vendor.pb.dart';
|
||||
|
||||
@@ -1,965 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: zwift.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'zwift.pbenum.dart';
|
||||
|
||||
export 'zwift.pbenum.dart';
|
||||
|
||||
/// The command code prepending this message is 0x07
|
||||
class PlayKeyPadStatus extends $pb.GeneratedMessage {
|
||||
factory PlayKeyPadStatus({
|
||||
PlayButtonStatus? rightPad,
|
||||
PlayButtonStatus? buttonYUp,
|
||||
PlayButtonStatus? buttonZLeft,
|
||||
PlayButtonStatus? buttonARight,
|
||||
PlayButtonStatus? buttonBDown,
|
||||
PlayButtonStatus? buttonShift,
|
||||
PlayButtonStatus? buttonOn,
|
||||
$core.int? analogLR,
|
||||
$core.int? analogUD,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (rightPad != null) {
|
||||
$result.rightPad = rightPad;
|
||||
}
|
||||
if (buttonYUp != null) {
|
||||
$result.buttonYUp = buttonYUp;
|
||||
}
|
||||
if (buttonZLeft != null) {
|
||||
$result.buttonZLeft = buttonZLeft;
|
||||
}
|
||||
if (buttonARight != null) {
|
||||
$result.buttonARight = buttonARight;
|
||||
}
|
||||
if (buttonBDown != null) {
|
||||
$result.buttonBDown = buttonBDown;
|
||||
}
|
||||
if (buttonShift != null) {
|
||||
$result.buttonShift = buttonShift;
|
||||
}
|
||||
if (buttonOn != null) {
|
||||
$result.buttonOn = buttonOn;
|
||||
}
|
||||
if (analogLR != null) {
|
||||
$result.analogLR = analogLR;
|
||||
}
|
||||
if (analogUD != null) {
|
||||
$result.analogUD = analogUD;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
PlayKeyPadStatus._() : super();
|
||||
factory PlayKeyPadStatus.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory PlayKeyPadStatus.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PlayKeyPadStatus', package: const $pb.PackageName(_omitMessageNames ? '' : 'de.jonasbark'), createEmptyInstance: create)
|
||||
..e<PlayButtonStatus>(1, _omitFieldNames ? '' : 'RightPad', $pb.PbFieldType.OE, protoName: 'RightPad', defaultOrMaker: PlayButtonStatus.ON, valueOf: PlayButtonStatus.valueOf, enumValues: PlayButtonStatus.values)
|
||||
..e<PlayButtonStatus>(2, _omitFieldNames ? '' : 'ButtonYUp', $pb.PbFieldType.OE, protoName: 'Button_Y_Up', defaultOrMaker: PlayButtonStatus.ON, valueOf: PlayButtonStatus.valueOf, enumValues: PlayButtonStatus.values)
|
||||
..e<PlayButtonStatus>(3, _omitFieldNames ? '' : 'ButtonZLeft', $pb.PbFieldType.OE, protoName: 'Button_Z_Left', defaultOrMaker: PlayButtonStatus.ON, valueOf: PlayButtonStatus.valueOf, enumValues: PlayButtonStatus.values)
|
||||
..e<PlayButtonStatus>(4, _omitFieldNames ? '' : 'ButtonARight', $pb.PbFieldType.OE, protoName: 'Button_A_Right', defaultOrMaker: PlayButtonStatus.ON, valueOf: PlayButtonStatus.valueOf, enumValues: PlayButtonStatus.values)
|
||||
..e<PlayButtonStatus>(5, _omitFieldNames ? '' : 'ButtonBDown', $pb.PbFieldType.OE, protoName: 'Button_B_Down', defaultOrMaker: PlayButtonStatus.ON, valueOf: PlayButtonStatus.valueOf, enumValues: PlayButtonStatus.values)
|
||||
..e<PlayButtonStatus>(6, _omitFieldNames ? '' : 'ButtonShift', $pb.PbFieldType.OE, protoName: 'Button_Shift', defaultOrMaker: PlayButtonStatus.ON, valueOf: PlayButtonStatus.valueOf, enumValues: PlayButtonStatus.values)
|
||||
..e<PlayButtonStatus>(7, _omitFieldNames ? '' : 'ButtonOn', $pb.PbFieldType.OE, protoName: 'Button_On', defaultOrMaker: PlayButtonStatus.ON, valueOf: PlayButtonStatus.valueOf, enumValues: PlayButtonStatus.values)
|
||||
..a<$core.int>(8, _omitFieldNames ? '' : 'AnalogLR', $pb.PbFieldType.OS3, protoName: 'Analog_LR')
|
||||
..a<$core.int>(9, _omitFieldNames ? '' : 'AnalogUD', $pb.PbFieldType.OS3, protoName: 'Analog_UD')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
PlayKeyPadStatus clone() => PlayKeyPadStatus()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
PlayKeyPadStatus copyWith(void Function(PlayKeyPadStatus) updates) => super.copyWith((message) => updates(message as PlayKeyPadStatus)) as PlayKeyPadStatus;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PlayKeyPadStatus create() => PlayKeyPadStatus._();
|
||||
PlayKeyPadStatus createEmptyInstance() => create();
|
||||
static $pb.PbList<PlayKeyPadStatus> createRepeated() => $pb.PbList<PlayKeyPadStatus>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PlayKeyPadStatus getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<PlayKeyPadStatus>(create);
|
||||
static PlayKeyPadStatus? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
PlayButtonStatus get rightPad => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set rightPad(PlayButtonStatus v) { setField(1, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasRightPad() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearRightPad() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
PlayButtonStatus get buttonYUp => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set buttonYUp(PlayButtonStatus v) { setField(2, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasButtonYUp() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearButtonYUp() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
PlayButtonStatus get buttonZLeft => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set buttonZLeft(PlayButtonStatus v) { setField(3, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasButtonZLeft() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearButtonZLeft() => clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
PlayButtonStatus get buttonARight => $_getN(3);
|
||||
@$pb.TagNumber(4)
|
||||
set buttonARight(PlayButtonStatus v) { setField(4, v); }
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasButtonARight() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearButtonARight() => clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
PlayButtonStatus get buttonBDown => $_getN(4);
|
||||
@$pb.TagNumber(5)
|
||||
set buttonBDown(PlayButtonStatus v) { setField(5, v); }
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasButtonBDown() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearButtonBDown() => clearField(5);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
PlayButtonStatus get buttonShift => $_getN(5);
|
||||
@$pb.TagNumber(6)
|
||||
set buttonShift(PlayButtonStatus v) { setField(6, v); }
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasButtonShift() => $_has(5);
|
||||
@$pb.TagNumber(6)
|
||||
void clearButtonShift() => clearField(6);
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
PlayButtonStatus get buttonOn => $_getN(6);
|
||||
@$pb.TagNumber(7)
|
||||
set buttonOn(PlayButtonStatus v) { setField(7, v); }
|
||||
@$pb.TagNumber(7)
|
||||
$core.bool hasButtonOn() => $_has(6);
|
||||
@$pb.TagNumber(7)
|
||||
void clearButtonOn() => clearField(7);
|
||||
|
||||
@$pb.TagNumber(8)
|
||||
$core.int get analogLR => $_getIZ(7);
|
||||
@$pb.TagNumber(8)
|
||||
set analogLR($core.int v) { $_setSignedInt32(7, v); }
|
||||
@$pb.TagNumber(8)
|
||||
$core.bool hasAnalogLR() => $_has(7);
|
||||
@$pb.TagNumber(8)
|
||||
void clearAnalogLR() => clearField(8);
|
||||
|
||||
@$pb.TagNumber(9)
|
||||
$core.int get analogUD => $_getIZ(8);
|
||||
@$pb.TagNumber(9)
|
||||
set analogUD($core.int v) { $_setSignedInt32(8, v); }
|
||||
@$pb.TagNumber(9)
|
||||
$core.bool hasAnalogUD() => $_has(8);
|
||||
@$pb.TagNumber(9)
|
||||
void clearAnalogUD() => clearField(9);
|
||||
}
|
||||
|
||||
class PlayCommandParameters extends $pb.GeneratedMessage {
|
||||
factory PlayCommandParameters({
|
||||
$core.int? param1,
|
||||
$core.int? param2,
|
||||
$core.int? hapticPattern,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (param1 != null) {
|
||||
$result.param1 = param1;
|
||||
}
|
||||
if (param2 != null) {
|
||||
$result.param2 = param2;
|
||||
}
|
||||
if (hapticPattern != null) {
|
||||
$result.hapticPattern = hapticPattern;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
PlayCommandParameters._() : super();
|
||||
factory PlayCommandParameters.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory PlayCommandParameters.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PlayCommandParameters', package: const $pb.PackageName(_omitMessageNames ? '' : 'de.jonasbark'), createEmptyInstance: create)
|
||||
..a<$core.int>(1, _omitFieldNames ? '' : 'param1', $pb.PbFieldType.OU3)
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'param2', $pb.PbFieldType.OU3)
|
||||
..a<$core.int>(3, _omitFieldNames ? '' : 'HapticPattern', $pb.PbFieldType.OU3, protoName: 'HapticPattern')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
PlayCommandParameters clone() => PlayCommandParameters()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
PlayCommandParameters copyWith(void Function(PlayCommandParameters) updates) => super.copyWith((message) => updates(message as PlayCommandParameters)) as PlayCommandParameters;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PlayCommandParameters create() => PlayCommandParameters._();
|
||||
PlayCommandParameters createEmptyInstance() => create();
|
||||
static $pb.PbList<PlayCommandParameters> createRepeated() => $pb.PbList<PlayCommandParameters>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PlayCommandParameters getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<PlayCommandParameters>(create);
|
||||
static PlayCommandParameters? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.int get param1 => $_getIZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set param1($core.int v) { $_setUnsignedInt32(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasParam1() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearParam1() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.int get param2 => $_getIZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set param2($core.int v) { $_setUnsignedInt32(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasParam2() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearParam2() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.int get hapticPattern => $_getIZ(2);
|
||||
@$pb.TagNumber(3)
|
||||
set hapticPattern($core.int v) { $_setUnsignedInt32(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasHapticPattern() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearHapticPattern() => clearField(3);
|
||||
}
|
||||
|
||||
class PlayCommandContents extends $pb.GeneratedMessage {
|
||||
factory PlayCommandContents({
|
||||
PlayCommandParameters? commandParameters,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (commandParameters != null) {
|
||||
$result.commandParameters = commandParameters;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
PlayCommandContents._() : super();
|
||||
factory PlayCommandContents.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory PlayCommandContents.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PlayCommandContents', package: const $pb.PackageName(_omitMessageNames ? '' : 'de.jonasbark'), createEmptyInstance: create)
|
||||
..aOM<PlayCommandParameters>(1, _omitFieldNames ? '' : 'CommandParameters', protoName: 'CommandParameters', subBuilder: PlayCommandParameters.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
PlayCommandContents clone() => PlayCommandContents()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
PlayCommandContents copyWith(void Function(PlayCommandContents) updates) => super.copyWith((message) => updates(message as PlayCommandContents)) as PlayCommandContents;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PlayCommandContents create() => PlayCommandContents._();
|
||||
PlayCommandContents createEmptyInstance() => create();
|
||||
static $pb.PbList<PlayCommandContents> createRepeated() => $pb.PbList<PlayCommandContents>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PlayCommandContents getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<PlayCommandContents>(create);
|
||||
static PlayCommandContents? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
PlayCommandParameters get commandParameters => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set commandParameters(PlayCommandParameters v) { setField(1, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasCommandParameters() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearCommandParameters() => clearField(1);
|
||||
@$pb.TagNumber(1)
|
||||
PlayCommandParameters ensureCommandParameters() => $_ensure(0);
|
||||
}
|
||||
|
||||
/// The command code prepending this message is 0x12
|
||||
/// This is sent to the control point to configure and make the controller vibrate
|
||||
class PlayCommand extends $pb.GeneratedMessage {
|
||||
factory PlayCommand({
|
||||
PlayCommandContents? commandContents,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (commandContents != null) {
|
||||
$result.commandContents = commandContents;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
PlayCommand._() : super();
|
||||
factory PlayCommand.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory PlayCommand.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PlayCommand', package: const $pb.PackageName(_omitMessageNames ? '' : 'de.jonasbark'), createEmptyInstance: create)
|
||||
..aOM<PlayCommandContents>(2, _omitFieldNames ? '' : 'CommandContents', protoName: 'CommandContents', subBuilder: PlayCommandContents.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
PlayCommand clone() => PlayCommand()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
PlayCommand copyWith(void Function(PlayCommand) updates) => super.copyWith((message) => updates(message as PlayCommand)) as PlayCommand;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PlayCommand create() => PlayCommand._();
|
||||
PlayCommand createEmptyInstance() => create();
|
||||
static $pb.PbList<PlayCommand> createRepeated() => $pb.PbList<PlayCommand>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PlayCommand getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<PlayCommand>(create);
|
||||
static PlayCommand? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
PlayCommandContents get commandContents => $_getN(0);
|
||||
@$pb.TagNumber(2)
|
||||
set commandContents(PlayCommandContents v) { setField(2, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasCommandContents() => $_has(0);
|
||||
@$pb.TagNumber(2)
|
||||
void clearCommandContents() => clearField(2);
|
||||
@$pb.TagNumber(2)
|
||||
PlayCommandContents ensureCommandContents() => $_ensure(0);
|
||||
}
|
||||
|
||||
/// The command code prepending this message is 0x19
|
||||
/// This is sent periodically when there are no button presses
|
||||
class Idle extends $pb.GeneratedMessage {
|
||||
factory Idle({
|
||||
$core.int? unknown2,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (unknown2 != null) {
|
||||
$result.unknown2 = unknown2;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
Idle._() : super();
|
||||
factory Idle.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory Idle.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Idle', package: const $pb.PackageName(_omitMessageNames ? '' : 'de.jonasbark'), createEmptyInstance: create)
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'Unknown2', $pb.PbFieldType.OU3, protoName: 'Unknown2')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
Idle clone() => Idle()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
Idle copyWith(void Function(Idle) updates) => super.copyWith((message) => updates(message as Idle)) as Idle;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Idle create() => Idle._();
|
||||
Idle createEmptyInstance() => create();
|
||||
static $pb.PbList<Idle> createRepeated() => $pb.PbList<Idle>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Idle getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Idle>(create);
|
||||
static Idle? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.int get unknown2 => $_getIZ(0);
|
||||
@$pb.TagNumber(2)
|
||||
set unknown2($core.int v) { $_setUnsignedInt32(0, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasUnknown2() => $_has(0);
|
||||
@$pb.TagNumber(2)
|
||||
void clearUnknown2() => clearField(2);
|
||||
}
|
||||
|
||||
class RideAnalogKeyPress extends $pb.GeneratedMessage {
|
||||
factory RideAnalogKeyPress({
|
||||
RideAnalogLocation? location,
|
||||
$core.int? analogValue,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (location != null) {
|
||||
$result.location = location;
|
||||
}
|
||||
if (analogValue != null) {
|
||||
$result.analogValue = analogValue;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
RideAnalogKeyPress._() : super();
|
||||
factory RideAnalogKeyPress.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory RideAnalogKeyPress.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'RideAnalogKeyPress', package: const $pb.PackageName(_omitMessageNames ? '' : 'de.jonasbark'), createEmptyInstance: create)
|
||||
..e<RideAnalogLocation>(1, _omitFieldNames ? '' : 'Location', $pb.PbFieldType.OE, protoName: 'Location', defaultOrMaker: RideAnalogLocation.LEFT, valueOf: RideAnalogLocation.valueOf, enumValues: RideAnalogLocation.values)
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'AnalogValue', $pb.PbFieldType.OS3, protoName: 'AnalogValue')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
RideAnalogKeyPress clone() => RideAnalogKeyPress()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
RideAnalogKeyPress copyWith(void Function(RideAnalogKeyPress) updates) => super.copyWith((message) => updates(message as RideAnalogKeyPress)) as RideAnalogKeyPress;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static RideAnalogKeyPress create() => RideAnalogKeyPress._();
|
||||
RideAnalogKeyPress createEmptyInstance() => create();
|
||||
static $pb.PbList<RideAnalogKeyPress> createRepeated() => $pb.PbList<RideAnalogKeyPress>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static RideAnalogKeyPress getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RideAnalogKeyPress>(create);
|
||||
static RideAnalogKeyPress? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
RideAnalogLocation get location => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set location(RideAnalogLocation v) { setField(1, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasLocation() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearLocation() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.int get analogValue => $_getIZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set analogValue($core.int v) { $_setSignedInt32(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasAnalogValue() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearAnalogValue() => clearField(2);
|
||||
}
|
||||
|
||||
/// The command code prepending this message is 0x23
|
||||
/// All analog paddles (L0-L3) appear as repeated RideAnalogKeyPress in field 3
|
||||
class RideKeyPadStatus extends $pb.GeneratedMessage {
|
||||
factory RideKeyPadStatus({
|
||||
$core.int? buttonMap,
|
||||
$core.Iterable<RideAnalogKeyPress>? analogPaddles,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (buttonMap != null) {
|
||||
$result.buttonMap = buttonMap;
|
||||
}
|
||||
if (analogPaddles != null) {
|
||||
$result.analogPaddles.addAll(analogPaddles);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
RideKeyPadStatus._() : super();
|
||||
factory RideKeyPadStatus.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory RideKeyPadStatus.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'RideKeyPadStatus', package: const $pb.PackageName(_omitMessageNames ? '' : 'de.jonasbark'), createEmptyInstance: create)
|
||||
..a<$core.int>(1, _omitFieldNames ? '' : 'ButtonMap', $pb.PbFieldType.OU3, protoName: 'ButtonMap')
|
||||
..pc<RideAnalogKeyPress>(3, _omitFieldNames ? '' : 'AnalogPaddles', $pb.PbFieldType.PM, protoName: 'AnalogPaddles', subBuilder: RideAnalogKeyPress.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
RideKeyPadStatus clone() => RideKeyPadStatus()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
RideKeyPadStatus copyWith(void Function(RideKeyPadStatus) updates) => super.copyWith((message) => updates(message as RideKeyPadStatus)) as RideKeyPadStatus;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static RideKeyPadStatus create() => RideKeyPadStatus._();
|
||||
RideKeyPadStatus createEmptyInstance() => create();
|
||||
static $pb.PbList<RideKeyPadStatus> createRepeated() => $pb.PbList<RideKeyPadStatus>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static RideKeyPadStatus getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RideKeyPadStatus>(create);
|
||||
static RideKeyPadStatus? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.int get buttonMap => $_getIZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set buttonMap($core.int v) { $_setUnsignedInt32(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasButtonMap() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearButtonMap() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<RideAnalogKeyPress> get analogPaddles => $_getList(1);
|
||||
}
|
||||
|
||||
/// ------------------ Zwift Click messages
|
||||
/// The command code prepending this message is 0x37
|
||||
class ClickKeyPadStatus extends $pb.GeneratedMessage {
|
||||
factory ClickKeyPadStatus({
|
||||
PlayButtonStatus? buttonPlus,
|
||||
PlayButtonStatus? buttonMinus,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (buttonPlus != null) {
|
||||
$result.buttonPlus = buttonPlus;
|
||||
}
|
||||
if (buttonMinus != null) {
|
||||
$result.buttonMinus = buttonMinus;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
ClickKeyPadStatus._() : super();
|
||||
factory ClickKeyPadStatus.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory ClickKeyPadStatus.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ClickKeyPadStatus', package: const $pb.PackageName(_omitMessageNames ? '' : 'de.jonasbark'), createEmptyInstance: create)
|
||||
..e<PlayButtonStatus>(1, _omitFieldNames ? '' : 'ButtonPlus', $pb.PbFieldType.OE, protoName: 'Button_Plus', defaultOrMaker: PlayButtonStatus.ON, valueOf: PlayButtonStatus.valueOf, enumValues: PlayButtonStatus.values)
|
||||
..e<PlayButtonStatus>(2, _omitFieldNames ? '' : 'ButtonMinus', $pb.PbFieldType.OE, protoName: 'Button_Minus', defaultOrMaker: PlayButtonStatus.ON, valueOf: PlayButtonStatus.valueOf, enumValues: PlayButtonStatus.values)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
ClickKeyPadStatus clone() => ClickKeyPadStatus()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
ClickKeyPadStatus copyWith(void Function(ClickKeyPadStatus) updates) => super.copyWith((message) => updates(message as ClickKeyPadStatus)) as ClickKeyPadStatus;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ClickKeyPadStatus create() => ClickKeyPadStatus._();
|
||||
ClickKeyPadStatus createEmptyInstance() => create();
|
||||
static $pb.PbList<ClickKeyPadStatus> createRepeated() => $pb.PbList<ClickKeyPadStatus>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ClickKeyPadStatus getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ClickKeyPadStatus>(create);
|
||||
static ClickKeyPadStatus? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
PlayButtonStatus get buttonPlus => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set buttonPlus(PlayButtonStatus v) { setField(1, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasButtonPlus() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearButtonPlus() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
PlayButtonStatus get buttonMinus => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set buttonMinus(PlayButtonStatus v) { setField(2, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasButtonMinus() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearButtonMinus() => clearField(2);
|
||||
}
|
||||
|
||||
/// ------------------ Device Information requested after connection
|
||||
/// The command code prepending this message is 0x3c
|
||||
class DeviceInformationContent extends $pb.GeneratedMessage {
|
||||
factory DeviceInformationContent({
|
||||
$core.int? unknown1,
|
||||
$core.Iterable<$core.int>? softwareVersion,
|
||||
$core.String? deviceName,
|
||||
$core.int? unknown4,
|
||||
$core.int? unknown5,
|
||||
$core.String? serialNumber,
|
||||
$core.String? hardwareVersion,
|
||||
$core.Iterable<$core.int>? replyData,
|
||||
$core.int? unknown9,
|
||||
$core.int? unknown10,
|
||||
$core.int? unknown13,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (unknown1 != null) {
|
||||
$result.unknown1 = unknown1;
|
||||
}
|
||||
if (softwareVersion != null) {
|
||||
$result.softwareVersion.addAll(softwareVersion);
|
||||
}
|
||||
if (deviceName != null) {
|
||||
$result.deviceName = deviceName;
|
||||
}
|
||||
if (unknown4 != null) {
|
||||
$result.unknown4 = unknown4;
|
||||
}
|
||||
if (unknown5 != null) {
|
||||
$result.unknown5 = unknown5;
|
||||
}
|
||||
if (serialNumber != null) {
|
||||
$result.serialNumber = serialNumber;
|
||||
}
|
||||
if (hardwareVersion != null) {
|
||||
$result.hardwareVersion = hardwareVersion;
|
||||
}
|
||||
if (replyData != null) {
|
||||
$result.replyData.addAll(replyData);
|
||||
}
|
||||
if (unknown9 != null) {
|
||||
$result.unknown9 = unknown9;
|
||||
}
|
||||
if (unknown10 != null) {
|
||||
$result.unknown10 = unknown10;
|
||||
}
|
||||
if (unknown13 != null) {
|
||||
$result.unknown13 = unknown13;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
DeviceInformationContent._() : super();
|
||||
factory DeviceInformationContent.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory DeviceInformationContent.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'DeviceInformationContent', package: const $pb.PackageName(_omitMessageNames ? '' : 'de.jonasbark'), createEmptyInstance: create)
|
||||
..a<$core.int>(1, _omitFieldNames ? '' : 'Unknown1', $pb.PbFieldType.OU3, protoName: 'Unknown1')
|
||||
..p<$core.int>(2, _omitFieldNames ? '' : 'SoftwareVersion', $pb.PbFieldType.PU3, protoName: 'SoftwareVersion')
|
||||
..aOS(3, _omitFieldNames ? '' : 'DeviceName', protoName: 'DeviceName')
|
||||
..a<$core.int>(4, _omitFieldNames ? '' : 'Unknown4', $pb.PbFieldType.OU3, protoName: 'Unknown4')
|
||||
..a<$core.int>(5, _omitFieldNames ? '' : 'Unknown5', $pb.PbFieldType.OU3, protoName: 'Unknown5')
|
||||
..aOS(6, _omitFieldNames ? '' : 'SerialNumber', protoName: 'SerialNumber')
|
||||
..aOS(7, _omitFieldNames ? '' : 'HardwareVersion', protoName: 'HardwareVersion')
|
||||
..p<$core.int>(8, _omitFieldNames ? '' : 'ReplyData', $pb.PbFieldType.PU3, protoName: 'ReplyData')
|
||||
..a<$core.int>(9, _omitFieldNames ? '' : 'Unknown9', $pb.PbFieldType.OU3, protoName: 'Unknown9')
|
||||
..a<$core.int>(10, _omitFieldNames ? '' : 'Unknown10', $pb.PbFieldType.OU3, protoName: 'Unknown10')
|
||||
..a<$core.int>(13, _omitFieldNames ? '' : 'Unknown13', $pb.PbFieldType.OU3, protoName: 'Unknown13')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
DeviceInformationContent clone() => DeviceInformationContent()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
DeviceInformationContent copyWith(void Function(DeviceInformationContent) updates) => super.copyWith((message) => updates(message as DeviceInformationContent)) as DeviceInformationContent;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DeviceInformationContent create() => DeviceInformationContent._();
|
||||
DeviceInformationContent createEmptyInstance() => create();
|
||||
static $pb.PbList<DeviceInformationContent> createRepeated() => $pb.PbList<DeviceInformationContent>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DeviceInformationContent getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DeviceInformationContent>(create);
|
||||
static DeviceInformationContent? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.int get unknown1 => $_getIZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set unknown1($core.int v) { $_setUnsignedInt32(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasUnknown1() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearUnknown1() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get softwareVersion => $_getList(1);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.String get deviceName => $_getSZ(2);
|
||||
@$pb.TagNumber(3)
|
||||
set deviceName($core.String v) { $_setString(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasDeviceName() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearDeviceName() => clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.int get unknown4 => $_getIZ(3);
|
||||
@$pb.TagNumber(4)
|
||||
set unknown4($core.int v) { $_setUnsignedInt32(3, v); }
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasUnknown4() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearUnknown4() => clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.int get unknown5 => $_getIZ(4);
|
||||
@$pb.TagNumber(5)
|
||||
set unknown5($core.int v) { $_setUnsignedInt32(4, v); }
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasUnknown5() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearUnknown5() => clearField(5);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$core.String get serialNumber => $_getSZ(5);
|
||||
@$pb.TagNumber(6)
|
||||
set serialNumber($core.String v) { $_setString(5, v); }
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasSerialNumber() => $_has(5);
|
||||
@$pb.TagNumber(6)
|
||||
void clearSerialNumber() => clearField(6);
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
$core.String get hardwareVersion => $_getSZ(6);
|
||||
@$pb.TagNumber(7)
|
||||
set hardwareVersion($core.String v) { $_setString(6, v); }
|
||||
@$pb.TagNumber(7)
|
||||
$core.bool hasHardwareVersion() => $_has(6);
|
||||
@$pb.TagNumber(7)
|
||||
void clearHardwareVersion() => clearField(7);
|
||||
|
||||
@$pb.TagNumber(8)
|
||||
$core.List<$core.int> get replyData => $_getList(7);
|
||||
|
||||
@$pb.TagNumber(9)
|
||||
$core.int get unknown9 => $_getIZ(8);
|
||||
@$pb.TagNumber(9)
|
||||
set unknown9($core.int v) { $_setUnsignedInt32(8, v); }
|
||||
@$pb.TagNumber(9)
|
||||
$core.bool hasUnknown9() => $_has(8);
|
||||
@$pb.TagNumber(9)
|
||||
void clearUnknown9() => clearField(9);
|
||||
|
||||
@$pb.TagNumber(10)
|
||||
$core.int get unknown10 => $_getIZ(9);
|
||||
@$pb.TagNumber(10)
|
||||
set unknown10($core.int v) { $_setUnsignedInt32(9, v); }
|
||||
@$pb.TagNumber(10)
|
||||
$core.bool hasUnknown10() => $_has(9);
|
||||
@$pb.TagNumber(10)
|
||||
void clearUnknown10() => clearField(10);
|
||||
|
||||
@$pb.TagNumber(13)
|
||||
$core.int get unknown13 => $_getIZ(10);
|
||||
@$pb.TagNumber(13)
|
||||
set unknown13($core.int v) { $_setUnsignedInt32(10, v); }
|
||||
@$pb.TagNumber(13)
|
||||
$core.bool hasUnknown13() => $_has(10);
|
||||
@$pb.TagNumber(13)
|
||||
void clearUnknown13() => clearField(13);
|
||||
}
|
||||
|
||||
class SubContent extends $pb.GeneratedMessage {
|
||||
factory SubContent({
|
||||
DeviceInformationContent? content,
|
||||
$core.int? unknown2,
|
||||
$core.int? unknown4,
|
||||
$core.int? unknown5,
|
||||
$core.int? unknown6,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (content != null) {
|
||||
$result.content = content;
|
||||
}
|
||||
if (unknown2 != null) {
|
||||
$result.unknown2 = unknown2;
|
||||
}
|
||||
if (unknown4 != null) {
|
||||
$result.unknown4 = unknown4;
|
||||
}
|
||||
if (unknown5 != null) {
|
||||
$result.unknown5 = unknown5;
|
||||
}
|
||||
if (unknown6 != null) {
|
||||
$result.unknown6 = unknown6;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
SubContent._() : super();
|
||||
factory SubContent.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory SubContent.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SubContent', package: const $pb.PackageName(_omitMessageNames ? '' : 'de.jonasbark'), createEmptyInstance: create)
|
||||
..aOM<DeviceInformationContent>(1, _omitFieldNames ? '' : 'Content', protoName: 'Content', subBuilder: DeviceInformationContent.create)
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'Unknown2', $pb.PbFieldType.OU3, protoName: 'Unknown2')
|
||||
..a<$core.int>(4, _omitFieldNames ? '' : 'Unknown4', $pb.PbFieldType.OU3, protoName: 'Unknown4')
|
||||
..a<$core.int>(5, _omitFieldNames ? '' : 'Unknown5', $pb.PbFieldType.OU3, protoName: 'Unknown5')
|
||||
..a<$core.int>(6, _omitFieldNames ? '' : 'Unknown6', $pb.PbFieldType.OU3, protoName: 'Unknown6')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
SubContent clone() => SubContent()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
SubContent copyWith(void Function(SubContent) updates) => super.copyWith((message) => updates(message as SubContent)) as SubContent;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SubContent create() => SubContent._();
|
||||
SubContent createEmptyInstance() => create();
|
||||
static $pb.PbList<SubContent> createRepeated() => $pb.PbList<SubContent>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SubContent getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SubContent>(create);
|
||||
static SubContent? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
DeviceInformationContent get content => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set content(DeviceInformationContent v) { setField(1, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasContent() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearContent() => clearField(1);
|
||||
@$pb.TagNumber(1)
|
||||
DeviceInformationContent ensureContent() => $_ensure(0);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.int get unknown2 => $_getIZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set unknown2($core.int v) { $_setUnsignedInt32(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasUnknown2() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearUnknown2() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.int get unknown4 => $_getIZ(2);
|
||||
@$pb.TagNumber(4)
|
||||
set unknown4($core.int v) { $_setUnsignedInt32(2, v); }
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasUnknown4() => $_has(2);
|
||||
@$pb.TagNumber(4)
|
||||
void clearUnknown4() => clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.int get unknown5 => $_getIZ(3);
|
||||
@$pb.TagNumber(5)
|
||||
set unknown5($core.int v) { $_setUnsignedInt32(3, v); }
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasUnknown5() => $_has(3);
|
||||
@$pb.TagNumber(5)
|
||||
void clearUnknown5() => clearField(5);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$core.int get unknown6 => $_getIZ(4);
|
||||
@$pb.TagNumber(6)
|
||||
set unknown6($core.int v) { $_setUnsignedInt32(4, v); }
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasUnknown6() => $_has(4);
|
||||
@$pb.TagNumber(6)
|
||||
void clearUnknown6() => clearField(6);
|
||||
}
|
||||
|
||||
class DeviceInformation extends $pb.GeneratedMessage {
|
||||
factory DeviceInformation({
|
||||
$core.int? informationId,
|
||||
SubContent? subContent,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (informationId != null) {
|
||||
$result.informationId = informationId;
|
||||
}
|
||||
if (subContent != null) {
|
||||
$result.subContent = subContent;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
DeviceInformation._() : super();
|
||||
factory DeviceInformation.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory DeviceInformation.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'DeviceInformation', package: const $pb.PackageName(_omitMessageNames ? '' : 'de.jonasbark'), createEmptyInstance: create)
|
||||
..a<$core.int>(1, _omitFieldNames ? '' : 'InformationId', $pb.PbFieldType.OU3, protoName: 'InformationId')
|
||||
..aOM<SubContent>(2, _omitFieldNames ? '' : 'SubContent', protoName: 'SubContent', subBuilder: SubContent.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
DeviceInformation clone() => DeviceInformation()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
DeviceInformation copyWith(void Function(DeviceInformation) updates) => super.copyWith((message) => updates(message as DeviceInformation)) as DeviceInformation;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DeviceInformation create() => DeviceInformation._();
|
||||
DeviceInformation createEmptyInstance() => create();
|
||||
static $pb.PbList<DeviceInformation> createRepeated() => $pb.PbList<DeviceInformation>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static DeviceInformation getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DeviceInformation>(create);
|
||||
static DeviceInformation? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.int get informationId => $_getIZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set informationId($core.int v) { $_setUnsignedInt32(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasInformationId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearInformationId() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
SubContent get subContent => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set subContent(SubContent v) { setField(2, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasSubContent() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearSubContent() => clearField(2);
|
||||
@$pb.TagNumber(2)
|
||||
SubContent ensureSubContent() => $_ensure(1);
|
||||
}
|
||||
|
||||
|
||||
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
|
||||
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
|
||||
@@ -1,95 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: zwift.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
class PlayButtonStatus extends $pb.ProtobufEnum {
|
||||
static const PlayButtonStatus ON = PlayButtonStatus._(0, _omitEnumNames ? '' : 'ON');
|
||||
static const PlayButtonStatus OFF = PlayButtonStatus._(1, _omitEnumNames ? '' : 'OFF');
|
||||
|
||||
static const $core.List<PlayButtonStatus> values = <PlayButtonStatus> [
|
||||
ON,
|
||||
OFF,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, PlayButtonStatus> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static PlayButtonStatus? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const PlayButtonStatus._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
/// ----------------- Zwift Ride messages
|
||||
class RideButtonMask extends $pb.ProtobufEnum {
|
||||
static const RideButtonMask LEFT_BTN = RideButtonMask._(1, _omitEnumNames ? '' : 'LEFT_BTN');
|
||||
static const RideButtonMask UP_BTN = RideButtonMask._(2, _omitEnumNames ? '' : 'UP_BTN');
|
||||
static const RideButtonMask RIGHT_BTN = RideButtonMask._(4, _omitEnumNames ? '' : 'RIGHT_BTN');
|
||||
static const RideButtonMask DOWN_BTN = RideButtonMask._(8, _omitEnumNames ? '' : 'DOWN_BTN');
|
||||
static const RideButtonMask A_BTN = RideButtonMask._(16, _omitEnumNames ? '' : 'A_BTN');
|
||||
static const RideButtonMask B_BTN = RideButtonMask._(32, _omitEnumNames ? '' : 'B_BTN');
|
||||
static const RideButtonMask Y_BTN = RideButtonMask._(64, _omitEnumNames ? '' : 'Y_BTN');
|
||||
static const RideButtonMask Z_BTN = RideButtonMask._(256, _omitEnumNames ? '' : 'Z_BTN');
|
||||
static const RideButtonMask SHFT_UP_L_BTN = RideButtonMask._(512, _omitEnumNames ? '' : 'SHFT_UP_L_BTN');
|
||||
static const RideButtonMask SHFT_DN_L_BTN = RideButtonMask._(1024, _omitEnumNames ? '' : 'SHFT_DN_L_BTN');
|
||||
static const RideButtonMask POWERUP_L_BTN = RideButtonMask._(2048, _omitEnumNames ? '' : 'POWERUP_L_BTN');
|
||||
static const RideButtonMask ONOFF_L_BTN = RideButtonMask._(4096, _omitEnumNames ? '' : 'ONOFF_L_BTN');
|
||||
static const RideButtonMask SHFT_UP_R_BTN = RideButtonMask._(8192, _omitEnumNames ? '' : 'SHFT_UP_R_BTN');
|
||||
static const RideButtonMask SHFT_DN_R_BTN = RideButtonMask._(16384, _omitEnumNames ? '' : 'SHFT_DN_R_BTN');
|
||||
static const RideButtonMask POWERUP_R_BTN = RideButtonMask._(65536, _omitEnumNames ? '' : 'POWERUP_R_BTN');
|
||||
static const RideButtonMask ONOFF_R_BTN = RideButtonMask._(131072, _omitEnumNames ? '' : 'ONOFF_R_BTN');
|
||||
|
||||
static const $core.List<RideButtonMask> values = <RideButtonMask> [
|
||||
LEFT_BTN,
|
||||
UP_BTN,
|
||||
RIGHT_BTN,
|
||||
DOWN_BTN,
|
||||
A_BTN,
|
||||
B_BTN,
|
||||
Y_BTN,
|
||||
Z_BTN,
|
||||
SHFT_UP_L_BTN,
|
||||
SHFT_DN_L_BTN,
|
||||
POWERUP_L_BTN,
|
||||
ONOFF_L_BTN,
|
||||
SHFT_UP_R_BTN,
|
||||
SHFT_DN_R_BTN,
|
||||
POWERUP_R_BTN,
|
||||
ONOFF_R_BTN,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, RideButtonMask> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static RideButtonMask? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const RideButtonMask._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class RideAnalogLocation extends $pb.ProtobufEnum {
|
||||
static const RideAnalogLocation LEFT = RideAnalogLocation._(0, _omitEnumNames ? '' : 'LEFT');
|
||||
static const RideAnalogLocation RIGHT = RideAnalogLocation._(1, _omitEnumNames ? '' : 'RIGHT');
|
||||
static const RideAnalogLocation UP = RideAnalogLocation._(2, _omitEnumNames ? '' : 'UP');
|
||||
static const RideAnalogLocation DOWN = RideAnalogLocation._(3, _omitEnumNames ? '' : 'DOWN');
|
||||
|
||||
static const $core.List<RideAnalogLocation> values = <RideAnalogLocation> [
|
||||
LEFT,
|
||||
RIGHT,
|
||||
UP,
|
||||
DOWN,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, RideAnalogLocation> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static RideAnalogLocation? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const RideAnalogLocation._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
|
||||
const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names');
|
||||
@@ -1,264 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: zwift.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:convert' as $convert;
|
||||
import 'dart:core' as $core;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
|
||||
@$core.Deprecated('Use playButtonStatusDescriptor instead')
|
||||
const PlayButtonStatus$json = {
|
||||
'1': 'PlayButtonStatus',
|
||||
'2': [
|
||||
{'1': 'ON', '2': 0},
|
||||
{'1': 'OFF', '2': 1},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `PlayButtonStatus`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List playButtonStatusDescriptor = $convert.base64Decode(
|
||||
'ChBQbGF5QnV0dG9uU3RhdHVzEgYKAk9OEAASBwoDT0ZGEAE=');
|
||||
|
||||
@$core.Deprecated('Use rideButtonMaskDescriptor instead')
|
||||
const RideButtonMask$json = {
|
||||
'1': 'RideButtonMask',
|
||||
'2': [
|
||||
{'1': 'LEFT_BTN', '2': 1},
|
||||
{'1': 'UP_BTN', '2': 2},
|
||||
{'1': 'RIGHT_BTN', '2': 4},
|
||||
{'1': 'DOWN_BTN', '2': 8},
|
||||
{'1': 'A_BTN', '2': 16},
|
||||
{'1': 'B_BTN', '2': 32},
|
||||
{'1': 'Y_BTN', '2': 64},
|
||||
{'1': 'Z_BTN', '2': 256},
|
||||
{'1': 'SHFT_UP_L_BTN', '2': 512},
|
||||
{'1': 'SHFT_DN_L_BTN', '2': 1024},
|
||||
{'1': 'POWERUP_L_BTN', '2': 2048},
|
||||
{'1': 'ONOFF_L_BTN', '2': 4096},
|
||||
{'1': 'SHFT_UP_R_BTN', '2': 8192},
|
||||
{'1': 'SHFT_DN_R_BTN', '2': 16384},
|
||||
{'1': 'POWERUP_R_BTN', '2': 65536},
|
||||
{'1': 'ONOFF_R_BTN', '2': 131072},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RideButtonMask`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List rideButtonMaskDescriptor = $convert.base64Decode(
|
||||
'Cg5SaWRlQnV0dG9uTWFzaxIMCghMRUZUX0JUThABEgoKBlVQX0JUThACEg0KCVJJR0hUX0JUTh'
|
||||
'AEEgwKCERPV05fQlROEAgSCQoFQV9CVE4QEBIJCgVCX0JUThAgEgkKBVlfQlROEEASCgoFWl9C'
|
||||
'VE4QgAISEgoNU0hGVF9VUF9MX0JUThCABBISCg1TSEZUX0ROX0xfQlROEIAIEhIKDVBPV0VSVV'
|
||||
'BfTF9CVE4QgBASEAoLT05PRkZfTF9CVE4QgCASEgoNU0hGVF9VUF9SX0JUThCAQBITCg1TSEZU'
|
||||
'X0ROX1JfQlROEICAARITCg1QT1dFUlVQX1JfQlROEICABBIRCgtPTk9GRl9SX0JUThCAgAg=');
|
||||
|
||||
@$core.Deprecated('Use rideAnalogLocationDescriptor instead')
|
||||
const RideAnalogLocation$json = {
|
||||
'1': 'RideAnalogLocation',
|
||||
'2': [
|
||||
{'1': 'LEFT', '2': 0},
|
||||
{'1': 'RIGHT', '2': 1},
|
||||
{'1': 'UP', '2': 2},
|
||||
{'1': 'DOWN', '2': 3},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RideAnalogLocation`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List rideAnalogLocationDescriptor = $convert.base64Decode(
|
||||
'ChJSaWRlQW5hbG9nTG9jYXRpb24SCAoETEVGVBAAEgkKBVJJR0hUEAESBgoCVVAQAhIICgRET1'
|
||||
'dOEAM=');
|
||||
|
||||
@$core.Deprecated('Use playKeyPadStatusDescriptor instead')
|
||||
const PlayKeyPadStatus$json = {
|
||||
'1': 'PlayKeyPadStatus',
|
||||
'2': [
|
||||
{'1': 'RightPad', '3': 1, '4': 1, '5': 14, '6': '.de.jonasbark.PlayButtonStatus', '10': 'RightPad'},
|
||||
{'1': 'Button_Y_Up', '3': 2, '4': 1, '5': 14, '6': '.de.jonasbark.PlayButtonStatus', '10': 'ButtonYUp'},
|
||||
{'1': 'Button_Z_Left', '3': 3, '4': 1, '5': 14, '6': '.de.jonasbark.PlayButtonStatus', '10': 'ButtonZLeft'},
|
||||
{'1': 'Button_A_Right', '3': 4, '4': 1, '5': 14, '6': '.de.jonasbark.PlayButtonStatus', '10': 'ButtonARight'},
|
||||
{'1': 'Button_B_Down', '3': 5, '4': 1, '5': 14, '6': '.de.jonasbark.PlayButtonStatus', '10': 'ButtonBDown'},
|
||||
{'1': 'Button_Shift', '3': 6, '4': 1, '5': 14, '6': '.de.jonasbark.PlayButtonStatus', '10': 'ButtonShift'},
|
||||
{'1': 'Button_On', '3': 7, '4': 1, '5': 14, '6': '.de.jonasbark.PlayButtonStatus', '10': 'ButtonOn'},
|
||||
{'1': 'Analog_LR', '3': 8, '4': 1, '5': 17, '10': 'AnalogLR'},
|
||||
{'1': 'Analog_UD', '3': 9, '4': 1, '5': 17, '10': 'AnalogUD'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `PlayKeyPadStatus`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List playKeyPadStatusDescriptor = $convert.base64Decode(
|
||||
'ChBQbGF5S2V5UGFkU3RhdHVzEjoKCFJpZ2h0UGFkGAEgASgOMh4uZGUuam9uYXNiYXJrLlBsYX'
|
||||
'lCdXR0b25TdGF0dXNSCFJpZ2h0UGFkEj4KC0J1dHRvbl9ZX1VwGAIgASgOMh4uZGUuam9uYXNi'
|
||||
'YXJrLlBsYXlCdXR0b25TdGF0dXNSCUJ1dHRvbllVcBJCCg1CdXR0b25fWl9MZWZ0GAMgASgOMh'
|
||||
'4uZGUuam9uYXNiYXJrLlBsYXlCdXR0b25TdGF0dXNSC0J1dHRvblpMZWZ0EkQKDkJ1dHRvbl9B'
|
||||
'X1JpZ2h0GAQgASgOMh4uZGUuam9uYXNiYXJrLlBsYXlCdXR0b25TdGF0dXNSDEJ1dHRvbkFSaW'
|
||||
'dodBJCCg1CdXR0b25fQl9Eb3duGAUgASgOMh4uZGUuam9uYXNiYXJrLlBsYXlCdXR0b25TdGF0'
|
||||
'dXNSC0J1dHRvbkJEb3duEkEKDEJ1dHRvbl9TaGlmdBgGIAEoDjIeLmRlLmpvbmFzYmFyay5QbG'
|
||||
'F5QnV0dG9uU3RhdHVzUgtCdXR0b25TaGlmdBI7CglCdXR0b25fT24YByABKA4yHi5kZS5qb25h'
|
||||
'c2JhcmsuUGxheUJ1dHRvblN0YXR1c1IIQnV0dG9uT24SGwoJQW5hbG9nX0xSGAggASgRUghBbm'
|
||||
'Fsb2dMUhIbCglBbmFsb2dfVUQYCSABKBFSCEFuYWxvZ1VE');
|
||||
|
||||
@$core.Deprecated('Use playCommandParametersDescriptor instead')
|
||||
const PlayCommandParameters$json = {
|
||||
'1': 'PlayCommandParameters',
|
||||
'2': [
|
||||
{'1': 'param1', '3': 1, '4': 1, '5': 13, '10': 'param1'},
|
||||
{'1': 'param2', '3': 2, '4': 1, '5': 13, '10': 'param2'},
|
||||
{'1': 'HapticPattern', '3': 3, '4': 1, '5': 13, '10': 'HapticPattern'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `PlayCommandParameters`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List playCommandParametersDescriptor = $convert.base64Decode(
|
||||
'ChVQbGF5Q29tbWFuZFBhcmFtZXRlcnMSFgoGcGFyYW0xGAEgASgNUgZwYXJhbTESFgoGcGFyYW'
|
||||
'0yGAIgASgNUgZwYXJhbTISJAoNSGFwdGljUGF0dGVybhgDIAEoDVINSGFwdGljUGF0dGVybg==');
|
||||
|
||||
@$core.Deprecated('Use playCommandContentsDescriptor instead')
|
||||
const PlayCommandContents$json = {
|
||||
'1': 'PlayCommandContents',
|
||||
'2': [
|
||||
{'1': 'CommandParameters', '3': 1, '4': 1, '5': 11, '6': '.de.jonasbark.PlayCommandParameters', '10': 'CommandParameters'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `PlayCommandContents`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List playCommandContentsDescriptor = $convert.base64Decode(
|
||||
'ChNQbGF5Q29tbWFuZENvbnRlbnRzElEKEUNvbW1hbmRQYXJhbWV0ZXJzGAEgASgLMiMuZGUuam'
|
||||
'9uYXNiYXJrLlBsYXlDb21tYW5kUGFyYW1ldGVyc1IRQ29tbWFuZFBhcmFtZXRlcnM=');
|
||||
|
||||
@$core.Deprecated('Use playCommandDescriptor instead')
|
||||
const PlayCommand$json = {
|
||||
'1': 'PlayCommand',
|
||||
'2': [
|
||||
{'1': 'CommandContents', '3': 2, '4': 1, '5': 11, '6': '.de.jonasbark.PlayCommandContents', '10': 'CommandContents'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `PlayCommand`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List playCommandDescriptor = $convert.base64Decode(
|
||||
'CgtQbGF5Q29tbWFuZBJLCg9Db21tYW5kQ29udGVudHMYAiABKAsyIS5kZS5qb25hc2JhcmsuUG'
|
||||
'xheUNvbW1hbmRDb250ZW50c1IPQ29tbWFuZENvbnRlbnRz');
|
||||
|
||||
@$core.Deprecated('Use idleDescriptor instead')
|
||||
const Idle$json = {
|
||||
'1': 'Idle',
|
||||
'2': [
|
||||
{'1': 'Unknown2', '3': 2, '4': 1, '5': 13, '10': 'Unknown2'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Idle`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List idleDescriptor = $convert.base64Decode(
|
||||
'CgRJZGxlEhoKCFVua25vd24yGAIgASgNUghVbmtub3duMg==');
|
||||
|
||||
@$core.Deprecated('Use rideAnalogKeyPressDescriptor instead')
|
||||
const RideAnalogKeyPress$json = {
|
||||
'1': 'RideAnalogKeyPress',
|
||||
'2': [
|
||||
{'1': 'Location', '3': 1, '4': 1, '5': 14, '6': '.de.jonasbark.RideAnalogLocation', '10': 'Location'},
|
||||
{'1': 'AnalogValue', '3': 2, '4': 1, '5': 17, '10': 'AnalogValue'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RideAnalogKeyPress`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List rideAnalogKeyPressDescriptor = $convert.base64Decode(
|
||||
'ChJSaWRlQW5hbG9nS2V5UHJlc3MSPAoITG9jYXRpb24YASABKA4yIC5kZS5qb25hc2JhcmsuUm'
|
||||
'lkZUFuYWxvZ0xvY2F0aW9uUghMb2NhdGlvbhIgCgtBbmFsb2dWYWx1ZRgCIAEoEVILQW5hbG9n'
|
||||
'VmFsdWU=');
|
||||
|
||||
@$core.Deprecated('Use rideKeyPadStatusDescriptor instead')
|
||||
const RideKeyPadStatus$json = {
|
||||
'1': 'RideKeyPadStatus',
|
||||
'2': [
|
||||
{'1': 'ButtonMap', '3': 1, '4': 1, '5': 13, '10': 'ButtonMap'},
|
||||
{'1': 'AnalogPaddles', '3': 3, '4': 3, '5': 11, '6': '.de.jonasbark.RideAnalogKeyPress', '10': 'AnalogPaddles'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RideKeyPadStatus`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List rideKeyPadStatusDescriptor = $convert.base64Decode(
|
||||
'ChBSaWRlS2V5UGFkU3RhdHVzEhwKCUJ1dHRvbk1hcBgBIAEoDVIJQnV0dG9uTWFwEkYKDUFuYW'
|
||||
'xvZ1BhZGRsZXMYAyADKAsyIC5kZS5qb25hc2JhcmsuUmlkZUFuYWxvZ0tleVByZXNzUg1BbmFs'
|
||||
'b2dQYWRkbGVz');
|
||||
|
||||
@$core.Deprecated('Use clickKeyPadStatusDescriptor instead')
|
||||
const ClickKeyPadStatus$json = {
|
||||
'1': 'ClickKeyPadStatus',
|
||||
'2': [
|
||||
{'1': 'Button_Plus', '3': 1, '4': 1, '5': 14, '6': '.de.jonasbark.PlayButtonStatus', '10': 'ButtonPlus'},
|
||||
{'1': 'Button_Minus', '3': 2, '4': 1, '5': 14, '6': '.de.jonasbark.PlayButtonStatus', '10': 'ButtonMinus'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ClickKeyPadStatus`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List clickKeyPadStatusDescriptor = $convert.base64Decode(
|
||||
'ChFDbGlja0tleVBhZFN0YXR1cxI/CgtCdXR0b25fUGx1cxgBIAEoDjIeLmRlLmpvbmFzYmFyay'
|
||||
'5QbGF5QnV0dG9uU3RhdHVzUgpCdXR0b25QbHVzEkEKDEJ1dHRvbl9NaW51cxgCIAEoDjIeLmRl'
|
||||
'LmpvbmFzYmFyay5QbGF5QnV0dG9uU3RhdHVzUgtCdXR0b25NaW51cw==');
|
||||
|
||||
@$core.Deprecated('Use deviceInformationContentDescriptor instead')
|
||||
const DeviceInformationContent$json = {
|
||||
'1': 'DeviceInformationContent',
|
||||
'2': [
|
||||
{'1': 'Unknown1', '3': 1, '4': 1, '5': 13, '10': 'Unknown1'},
|
||||
{'1': 'SoftwareVersion', '3': 2, '4': 3, '5': 13, '10': 'SoftwareVersion'},
|
||||
{'1': 'DeviceName', '3': 3, '4': 1, '5': 9, '10': 'DeviceName'},
|
||||
{'1': 'Unknown4', '3': 4, '4': 1, '5': 13, '10': 'Unknown4'},
|
||||
{'1': 'Unknown5', '3': 5, '4': 1, '5': 13, '10': 'Unknown5'},
|
||||
{'1': 'SerialNumber', '3': 6, '4': 1, '5': 9, '10': 'SerialNumber'},
|
||||
{'1': 'HardwareVersion', '3': 7, '4': 1, '5': 9, '10': 'HardwareVersion'},
|
||||
{'1': 'ReplyData', '3': 8, '4': 3, '5': 13, '10': 'ReplyData'},
|
||||
{'1': 'Unknown9', '3': 9, '4': 1, '5': 13, '10': 'Unknown9'},
|
||||
{'1': 'Unknown10', '3': 10, '4': 1, '5': 13, '10': 'Unknown10'},
|
||||
{'1': 'Unknown13', '3': 13, '4': 1, '5': 13, '10': 'Unknown13'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `DeviceInformationContent`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List deviceInformationContentDescriptor = $convert.base64Decode(
|
||||
'ChhEZXZpY2VJbmZvcm1hdGlvbkNvbnRlbnQSGgoIVW5rbm93bjEYASABKA1SCFVua25vd24xEi'
|
||||
'gKD1NvZnR3YXJlVmVyc2lvbhgCIAMoDVIPU29mdHdhcmVWZXJzaW9uEh4KCkRldmljZU5hbWUY'
|
||||
'AyABKAlSCkRldmljZU5hbWUSGgoIVW5rbm93bjQYBCABKA1SCFVua25vd240EhoKCFVua25vd2'
|
||||
'41GAUgASgNUghVbmtub3duNRIiCgxTZXJpYWxOdW1iZXIYBiABKAlSDFNlcmlhbE51bWJlchIo'
|
||||
'Cg9IYXJkd2FyZVZlcnNpb24YByABKAlSD0hhcmR3YXJlVmVyc2lvbhIcCglSZXBseURhdGEYCC'
|
||||
'ADKA1SCVJlcGx5RGF0YRIaCghVbmtub3duORgJIAEoDVIIVW5rbm93bjkSHAoJVW5rbm93bjEw'
|
||||
'GAogASgNUglVbmtub3duMTASHAoJVW5rbm93bjEzGA0gASgNUglVbmtub3duMTM=');
|
||||
|
||||
@$core.Deprecated('Use subContentDescriptor instead')
|
||||
const SubContent$json = {
|
||||
'1': 'SubContent',
|
||||
'2': [
|
||||
{'1': 'Content', '3': 1, '4': 1, '5': 11, '6': '.de.jonasbark.DeviceInformationContent', '10': 'Content'},
|
||||
{'1': 'Unknown2', '3': 2, '4': 1, '5': 13, '10': 'Unknown2'},
|
||||
{'1': 'Unknown4', '3': 4, '4': 1, '5': 13, '10': 'Unknown4'},
|
||||
{'1': 'Unknown5', '3': 5, '4': 1, '5': 13, '10': 'Unknown5'},
|
||||
{'1': 'Unknown6', '3': 6, '4': 1, '5': 13, '10': 'Unknown6'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `SubContent`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List subContentDescriptor = $convert.base64Decode(
|
||||
'CgpTdWJDb250ZW50EkAKB0NvbnRlbnQYASABKAsyJi5kZS5qb25hc2JhcmsuRGV2aWNlSW5mb3'
|
||||
'JtYXRpb25Db250ZW50UgdDb250ZW50EhoKCFVua25vd24yGAIgASgNUghVbmtub3duMhIaCghV'
|
||||
'bmtub3duNBgEIAEoDVIIVW5rbm93bjQSGgoIVW5rbm93bjUYBSABKA1SCFVua25vd241EhoKCF'
|
||||
'Vua25vd242GAYgASgNUghVbmtub3duNg==');
|
||||
|
||||
@$core.Deprecated('Use deviceInformationDescriptor instead')
|
||||
const DeviceInformation$json = {
|
||||
'1': 'DeviceInformation',
|
||||
'2': [
|
||||
{'1': 'InformationId', '3': 1, '4': 1, '5': 13, '10': 'InformationId'},
|
||||
{'1': 'SubContent', '3': 2, '4': 1, '5': 11, '6': '.de.jonasbark.SubContent', '10': 'SubContent'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `DeviceInformation`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List deviceInformationDescriptor = $convert.base64Decode(
|
||||
'ChFEZXZpY2VJbmZvcm1hdGlvbhIkCg1JbmZvcm1hdGlvbklkGAEgASgNUg1JbmZvcm1hdGlvbk'
|
||||
'lkEjgKClN1YkNvbnRlbnQYAiABKAsyGC5kZS5qb25hc2JhcmsuU3ViQ29udGVudFIKU3ViQ29u'
|
||||
'dGVudA==');
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: zwift.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
export 'zwift.pb.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zwift.pb.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/zwift_device.dart';
|
||||
import 'package:bike_control/utils/keymap/buttons.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
|
||||
import 'constants.dart';
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import 'package:bike_control/bluetooth/devices/zwift/constants.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pbenum.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/zwift_ride.dart';
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/pages/markdown.dart';
|
||||
import 'package:bike_control/pages/unlock.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/i18n_extension.dart';
|
||||
import 'package:bike_control/widgets/ui/warning.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:prop/emulators/ftms_emulator.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
final FtmsEmulator emulator = FtmsEmulator();
|
||||
|
||||
class ZwiftClickV2 extends ZwiftRide {
|
||||
ZwiftClickV2(super.scanResult)
|
||||
@@ -26,9 +30,9 @@ class ZwiftClickV2 extends ZwiftRide {
|
||||
ZwiftButtons.shiftUpLeft,
|
||||
ZwiftButtons.shiftUpRight,
|
||||
],
|
||||
);
|
||||
|
||||
bool _noLongerSendsEvents = false;
|
||||
) {
|
||||
emulator.setScanResult(scanResult);
|
||||
}
|
||||
|
||||
@override
|
||||
List<int> get startCommand => ZwiftConstants.RIDE_ON + ZwiftConstants.RESPONSE_START_CLICK_V2;
|
||||
@@ -44,23 +48,37 @@ class ZwiftClickV2 extends ZwiftRide {
|
||||
return "$name V2";
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setupHandshake() async {
|
||||
super.setupHandshake();
|
||||
await sendCommandBuffer(Uint8List.fromList([0xFF, 0x04, 0x00]));
|
||||
bool get isUnlocked {
|
||||
final lastUnlock = propPrefs.getZwiftClickV2LastUnlock(scanResult.deviceId);
|
||||
if (lastUnlock == null) {
|
||||
return false;
|
||||
}
|
||||
return lastUnlock > DateTime.now().subtract(const Duration(days: 1));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> processData(Uint8List bytes) {
|
||||
if (bytes.startsWith(ZwiftConstants.RESPONSE_STOPPED_CLICK_V2_VARIANT_1) ||
|
||||
bytes.startsWith(ZwiftConstants.RESPONSE_STOPPED_CLICK_V2_VARIANT_2)) {
|
||||
_noLongerSendsEvents = true;
|
||||
Future<void> setupHandshake() async {
|
||||
if (isUnlocked) {
|
||||
super.setupHandshake();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> handleServices(List<BleService> services) async {
|
||||
emulator.handleServices(services);
|
||||
await super.handleServices(services);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> processCharacteristic(String characteristic, Uint8List bytes) async {
|
||||
if (!emulator.processCharacteristic(characteristic, bytes)) {
|
||||
await super.processCharacteristic(characteristic, bytes);
|
||||
}
|
||||
return super.processData(bytes);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget showInformation(BuildContext context) {
|
||||
final lastUnlockDate = propPrefs.getZwiftClickV2LastUnlock(scanResult.deviceId);
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return Column(
|
||||
@@ -70,80 +88,98 @@ class ZwiftClickV2 extends ZwiftRide {
|
||||
children: [
|
||||
super.showInformation(context),
|
||||
|
||||
if (isConnected)
|
||||
if (core.settings.getShowZwiftClickV2ReconnectWarning())
|
||||
Stack(
|
||||
if (isConnected && !core.settings.getShowOnboarding())
|
||||
if (isUnlocked && lastUnlockDate != null)
|
||||
Warning(
|
||||
important: false,
|
||||
children: [
|
||||
Warning(
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Text(
|
||||
'Important Setup Information',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.destructive,
|
||||
),
|
||||
).small,
|
||||
Text(
|
||||
AppLocalizations.of(context).clickV2Instructions,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.destructive,
|
||||
),
|
||||
).xSmall,
|
||||
if (kDebugMode)
|
||||
GhostButton(
|
||||
onPressed: () {
|
||||
sendCommand(Opcode.RESET, null);
|
||||
},
|
||||
child: Text('Reset now'),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Icon(Icons.lock_open_rounded, color: Colors.white),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
AppLocalizations.of(context).unlock_unlockedUntilAroundDate(
|
||||
DateFormat('EEEE, HH:MM').format(lastUnlockDate.add(const Duration(days: 1))),
|
||||
),
|
||||
).xSmall,
|
||||
),
|
||||
Tooltip(
|
||||
tooltip: (c) => Text('Unlock again'),
|
||||
child: IconButton.ghost(
|
||||
icon: Icon(Icons.lock_reset_rounded),
|
||||
|
||||
Button.secondary(
|
||||
onPressed: () {
|
||||
openDrawer(
|
||||
context: context,
|
||||
position: OverlayPosition.bottom,
|
||||
builder: (_) => MarkdownPage(assetPath: 'TROUBLESHOOTING.md'),
|
||||
);
|
||||
},
|
||||
leading: const Icon(Icons.help_outline_outlined),
|
||||
child: Text(context.i18n.instructions),
|
||||
onPressed: () {
|
||||
openDrawer(
|
||||
context: context,
|
||||
position: OverlayPosition.bottom,
|
||||
builder: (_) => UnlockPage(device: this),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: IconButton.link(
|
||||
icon: Icon(
|
||||
Icons.close,
|
||||
color: Theme.of(context).colorScheme.destructive,
|
||||
),
|
||||
onPressed: () {
|
||||
core.settings.setShowZwiftClickV2ReconnectWarning(false);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
else
|
||||
Warning(
|
||||
important: false,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).clickV2EventInfo,
|
||||
).xSmall,
|
||||
LinkButton(
|
||||
child: Text(context.i18n.troubleshootingGuide),
|
||||
onPressed: () {
|
||||
openDrawer(
|
||||
context: context,
|
||||
position: OverlayPosition.bottom,
|
||||
builder: (_) => MarkdownPage(assetPath: 'TROUBLESHOOTING.md'),
|
||||
);
|
||||
},
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Icon(Icons.lock_rounded, color: Colors.white),
|
||||
),
|
||||
Flexible(child: Text(AppLocalizations.of(context).unlock_deviceIsCurrentlyLocked).xSmall),
|
||||
Button(
|
||||
onPressed: () {
|
||||
openDrawer(
|
||||
context: context,
|
||||
position: OverlayPosition.bottom,
|
||||
builder: (_) => UnlockPage(device: this),
|
||||
);
|
||||
},
|
||||
leading: const Icon(Icons.lock_open_rounded),
|
||||
style: ButtonStyle.primary(size: ButtonSize.small),
|
||||
child: Text(AppLocalizations.of(context).unlock_unlockNow),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
/*else
|
||||
Warning(
|
||||
important: false,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).clickV2EventInfo,
|
||||
).xSmall,
|
||||
LinkButton(
|
||||
child: Text(context.i18n.troubleshootingGuide),
|
||||
onPressed: () {
|
||||
openDrawer(
|
||||
context: context,
|
||||
position: OverlayPosition.bottom,
|
||||
builder: (_) => MarkdownPage(assetPath: 'TROUBLESHOOTING.md'),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),*/
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:async';
|
||||
|
||||
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';
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/i18n_extension.dart';
|
||||
@@ -10,6 +9,7 @@ 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:prop/prop.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
@@ -89,7 +89,7 @@ abstract class ZwiftDevice extends BluetoothDevice {
|
||||
|
||||
@override
|
||||
Future<void> processCharacteristic(String characteristic, Uint8List bytes) async {
|
||||
if (kDebugMode) {
|
||||
if (kDebugMode && false) {
|
||||
actionStreamInternal.add(
|
||||
LogNotification(
|
||||
"Received data on $characteristic: ${bytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ')}",
|
||||
|
||||
@@ -3,9 +3,6 @@ import 'dart:io';
|
||||
import 'package:bike_control/bluetooth/ble.dart';
|
||||
import 'package:bike_control/bluetooth/devices/trainer_connection.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/constants.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/ftms_mdns_emulator.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pb.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zwift.pbserver.dart' hide RideButtonMask;
|
||||
import 'package:bike_control/bluetooth/devices/zwift/zwift_ride.dart';
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
@@ -18,6 +15,7 @@ import 'package:bike_control/widgets/title.dart';
|
||||
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:prop/prop.dart' hide RideButtonMask;
|
||||
|
||||
class ZwiftEmulator extends TrainerConnection {
|
||||
bool get isLoading => _isLoading;
|
||||
@@ -163,7 +161,7 @@ class ZwiftEmulator extends TrainerConnection {
|
||||
);
|
||||
|
||||
final request = eventArgs.request;
|
||||
final response = handleWriteRequest(eventArgs.characteristic.uuid.toString(), request.value);
|
||||
final response = SharedLogic.handleWriteRequest(eventArgs.characteristic.uuid.toString(), request.value);
|
||||
if (response != null) {
|
||||
await _peripheralManager.notifyCharacteristic(
|
||||
_central!,
|
||||
@@ -362,106 +360,6 @@ class ZwiftEmulator extends TrainerConnection {
|
||||
return Success('Sent action: ${keyPair.inGameAction!.name}');
|
||||
}
|
||||
|
||||
Uint8List? handleWriteRequest(String characteristic, Uint8List value) {
|
||||
print(
|
||||
'Write request for characteristic: $characteristic',
|
||||
);
|
||||
|
||||
switch (characteristic.toUpperCase()) {
|
||||
case ZwiftConstants.ZWIFT_SYNC_RX_CHARACTERISTIC_UUID:
|
||||
print(
|
||||
'Handling write request for SYNC RX characteristic, value: ${value.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ')}\n${String.fromCharCodes(value)}',
|
||||
);
|
||||
|
||||
Opcode? opcode = Opcode.valueOf(value[0]);
|
||||
Uint8List message = value.sublist(1);
|
||||
|
||||
switch (opcode) {
|
||||
case Opcode.RIDE_ON:
|
||||
print('Sending handshake');
|
||||
return ZwiftConstants.RIDE_ON;
|
||||
case Opcode.GET:
|
||||
final response = Get.fromBuffer(message);
|
||||
final dataObjectType = DO.valueOf(response.dataObjectId);
|
||||
print('Received GET for data object: $dataObjectType');
|
||||
switch (dataObjectType) {
|
||||
case DO.PAGE_DEV_INFO:
|
||||
/*final devInfo = DevInfoPage(
|
||||
deviceName: 'Zwift Click'.codeUnits,
|
||||
deviceUid: '0B-58D15ABB4363'.codeUnits,
|
||||
manufacturerId: 0x01,
|
||||
serialNumber: '58D15ABB4363'.codeUnits,
|
||||
protocolVersion: 515,
|
||||
systemFwVersion: [0, 0, 1, 1],
|
||||
productId: 11,
|
||||
systemHwRevision: 'B.0'.codeUnits,
|
||||
deviceCapabilities: [DevInfoPage_DeviceCapabilities(deviceType: 2, capabilities: 1)],
|
||||
);
|
||||
final serverInfoResponse = Uint8List.fromList([
|
||||
Opcode.GET_RESPONSE.value,
|
||||
...GetResponse(
|
||||
dataObjectId: DO.PAGE_DEV_INFO.value,
|
||||
dataObjectData: devInfo.writeToBuffer(),
|
||||
).writeToBuffer(),
|
||||
]);*/
|
||||
// 3C080012460A440883041204000001011A0B5A7769667420436C69636B320F30422D3538443135414242343336333A03422E304204080210014801500B5A0C353844313541424234333633
|
||||
final expected = Uint8List.fromList(
|
||||
hexToBytes(
|
||||
'3C080012460A440883041204000001011A0B5A7769667420436C69636B320F30422D3538443135414242343336333A03422E304204080210014801500B5A0C353844313541424234333633',
|
||||
),
|
||||
);
|
||||
return expected;
|
||||
case DO.PAGE_CLIENT_SERVER_CONFIGURATION:
|
||||
final response = Uint8List.fromList([
|
||||
Opcode.GET_RESPONSE.value,
|
||||
...GetResponse(
|
||||
dataObjectId: DO.PAGE_CLIENT_SERVER_CONFIGURATION.value,
|
||||
dataObjectData: ClientServerCfgPage(
|
||||
notifications: 0,
|
||||
).writeToBuffer(),
|
||||
).writeToBuffer(),
|
||||
]);
|
||||
return response;
|
||||
case DO.PAGE_CONTROLLER_INPUT_CONFIG:
|
||||
final response = Uint8List.fromList([
|
||||
Opcode.GET_RESPONSE.value,
|
||||
...GetResponse(
|
||||
dataObjectId: DO.PAGE_CONTROLLER_INPUT_CONFIG.value,
|
||||
dataObjectData: ControllerInputConfigPage(
|
||||
supportedDigitalInputs: 4607,
|
||||
supportedAnalogInputs: 0,
|
||||
analogDeadZone: [],
|
||||
analogInputRange: [],
|
||||
).writeToBuffer(),
|
||||
).writeToBuffer(),
|
||||
]);
|
||||
return response;
|
||||
case DO.BATTERY_STATE:
|
||||
final response = Uint8List.fromList([
|
||||
Opcode.GET_RESPONSE.value,
|
||||
...GetResponse(
|
||||
dataObjectId: DO.BATTERY_STATE.value,
|
||||
dataObjectData: BatteryStatus(
|
||||
chgState: ChargingState.CHARGING_IDLE,
|
||||
percLevel: 100,
|
||||
timeToEmpty: 0,
|
||||
timeToFull: 0,
|
||||
).writeToBuffer(),
|
||||
).writeToBuffer(),
|
||||
]);
|
||||
return response;
|
||||
default:
|
||||
print('Unhandled data object type for GET: $dataObjectType');
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
print('Unhandled write request for characteristic: $characteristic $value');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
_peripheralManager.stopAdvertising();
|
||||
_peripheralManager.removeAllServices();
|
||||
|
||||
@@ -1,27 +1,34 @@
|
||||
import 'package:bike_control/bluetooth/devices/zwift/constants.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zwift.pb.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/zwift_device.dart';
|
||||
import 'package:bike_control/utils/keymap/buttons.dart';
|
||||
import 'package:bike_control/widgets/keymap_explanation.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
|
||||
class ZwiftPlay extends ZwiftDevice {
|
||||
ZwiftPlay(super.scanResult)
|
||||
final ZwiftDeviceType deviceType;
|
||||
|
||||
ZwiftPlay(super.scanResult, {required this.deviceType})
|
||||
: super(
|
||||
availableButtons: [
|
||||
ZwiftButtons.y,
|
||||
ZwiftButtons.z,
|
||||
ZwiftButtons.a,
|
||||
ZwiftButtons.b,
|
||||
ZwiftButtons.onOffRight,
|
||||
ZwiftButtons.sideButtonRight,
|
||||
ZwiftButtons.paddleRight,
|
||||
ZwiftButtons.navigationUp,
|
||||
ZwiftButtons.navigationLeft,
|
||||
ZwiftButtons.navigationRight,
|
||||
ZwiftButtons.navigationDown,
|
||||
ZwiftButtons.onOffLeft,
|
||||
ZwiftButtons.sideButtonLeft,
|
||||
ZwiftButtons.paddleLeft,
|
||||
if (deviceType == ZwiftDeviceType.playLeft) ...[
|
||||
ZwiftButtons.navigationUp,
|
||||
ZwiftButtons.navigationLeft,
|
||||
ZwiftButtons.navigationRight,
|
||||
ZwiftButtons.navigationDown,
|
||||
ZwiftButtons.onOffLeft,
|
||||
ZwiftButtons.sideButtonLeft,
|
||||
ZwiftButtons.paddleLeft,
|
||||
],
|
||||
if (deviceType == ZwiftDeviceType.playRight) ...[
|
||||
ZwiftButtons.y,
|
||||
ZwiftButtons.z,
|
||||
ZwiftButtons.a,
|
||||
ZwiftButtons.b,
|
||||
ZwiftButtons.onOffRight,
|
||||
ZwiftButtons.sideButtonRight,
|
||||
ZwiftButtons.paddleRight,
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
@@ -31,6 +38,9 @@ class ZwiftPlay extends ZwiftDevice {
|
||||
@override
|
||||
bool get canVibrate => true;
|
||||
|
||||
@override
|
||||
String get name => '${super.name} (${deviceType.name.splitByUpperCase().split(' ').last})';
|
||||
|
||||
@override
|
||||
List<ControllerButton> processClickNotification(Uint8List message) {
|
||||
final status = PlayKeyPadStatus.fromBuffer(message);
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
import 'package:bike_control/bluetooth/devices/zwift/constants.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pb.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp_vendor.pb.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zwift.pb.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/zwift_device.dart';
|
||||
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:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
class ZwiftRide extends ZwiftDevice {
|
||||
@@ -56,7 +54,7 @@ class ZwiftRide extends ZwiftDevice {
|
||||
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
'${DateTime.now().toString().split(" ").last} Received $opcode: ${bytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ')} => ${String.fromCharCodes(bytes)} ',
|
||||
'${DateTime.now().toString().split(" ").last} Received $opcode: ${bytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ')}',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pb.dart';
|
||||
import 'package:bike_control/utils/actions/base_actions.dart';
|
||||
import 'package:bike_control/utils/keymap/buttons.dart';
|
||||
import 'package:bike_control/widgets/keymap_explanation.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
|
||||
class BaseNotification {}
|
||||
|
||||
@@ -12,7 +12,7 @@ class LogNotification extends BaseNotification {
|
||||
|
||||
LogNotification(this.message) {
|
||||
if (kDebugMode) {
|
||||
print('LogNotification: $message');
|
||||
//print('LogNotification: $message');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bike_control/bluetooth/devices/trainer_connection.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pb.dart';
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/utils/actions/base_actions.dart';
|
||||
@@ -12,6 +11,7 @@ import 'package:bike_control/utils/requirements/multi.dart';
|
||||
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
|
||||
import '../utils/keymap/keymap.dart';
|
||||
|
||||
@@ -257,7 +257,7 @@ class RemotePairing extends TrainerConnection {
|
||||
: ''}',
|
||||
serviceUUIDs: [UUID.fromString(Platform.isIOS ? '1812' : '00001812-0000-1000-8000-00805F9B34FB')],
|
||||
);
|
||||
print('Starting advertising with Zwift service...');
|
||||
print('Starting advertising with Remote service...');
|
||||
|
||||
await _peripheralManager.startAdvertising(advertisement);
|
||||
_isLoading = false;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"accessibilityDescription": "BikeControl benötigt Zugriffsberechtigungen, um Ihre Trainings-Apps zu steuern.",
|
||||
"accessibilityDisclaimer": "BikeControl greift nur auf Ihren Bildschirm zu, um die von Ihnen konfigurierten Gesten auszuführen. Es werden keine weiteren Bedienungshilfen oder persönlichen Daten abgerufen.",
|
||||
"accessibilityReasonControl": "• Um Ihnen die Steuerung von Apps wie MyWhoosh, IndieVelo und anderen über Ihre Zwift-Geräte zu ermöglichen",
|
||||
"accessibilityReasonControl": "• Um Ihnen die Steuerung von Apps wie MyWhoosh, TrainingPeaks und anderen über Ihre Zwift-Geräte zu ermöglichen",
|
||||
"accessibilityReasonTouch": "• Um Berührungsgesten auf Ihrem Bildschirm zur Steuerung von Trainer-Apps zu simulieren.",
|
||||
"accessibilityReasonWindow": "• Um zu erkennen, welches Trainings-App-Fenster aktuell aktiv ist",
|
||||
"accessibilityServiceExplanation": "BikeControl benötigt die AccessibilityService API von Android, um ordnungsgemäß zu funktionieren.",
|
||||
@@ -23,6 +23,7 @@
|
||||
"allowsRunningInBackground": "Ermöglicht es BikeControl, im Hintergrund weiterzulaufen.",
|
||||
"alreadyBoughtTheApp": "App bereits gekauft? Dann musst Du BikeControl nicht erneut kaufen. Aus technischen Gründen lässt sich leider nicht feststellen, ob die App früher bereits erworben wurde. \n\nGebe Deine Play Store-Kauf-ID (z.B. GPA.3356-1337-1338-1339) ein, um die Vollversion freizuschalten. Falls Du diese nicht finden kannst, kontaktiere mich bitte direkt.",
|
||||
"alreadyBoughtTheAppPreviously": "App zuvor bereits gekauft?",
|
||||
"androidSystemAction": "Android-Systemaktion",
|
||||
"appIdActions": "{appId} Aktionen",
|
||||
"@appIdActions": {
|
||||
"placeholders": {
|
||||
@@ -45,7 +46,7 @@
|
||||
"chooseBikeControlInConnectionScreen": "Wähle im Verbindungsbildschirm BikeControl aus.",
|
||||
"clickAButtonOnYourController": "Klicke eine Controller-Taste, um deren Aktion zu bearbeiten, oder tippe auf das Bearbeitungssymbol.",
|
||||
"clickV2EventInfo": "Dein Click V2 sendet möglicherweise keine Tastenereignisse mehr. Probier mal ein paar Tasten aus und schau, ob sie in BikeControl angezeigt werden.",
|
||||
"clickV2Instructions": "Damit dein Zwift Click V2 optimal funktioniert, solltest du das Gerät vor jeder Trainings-Session in der Zwift-App verbinden.\nAnsonsten funktioniert der Click V2 nach einer Minute nicht mehr.\n\n1. Öffne die Zwift-App.\n2. Melde dich an (kein Abonnement nötig) und öffne den Bildschirm für die Geräteverbindung.\n3. Verbinde deinen Trainer und dann den Zwift Click V2.\n4. Schließe die Zwift-App wieder und verbinde dich erneut in BikeControl.",
|
||||
"clickV2Instructions": "Damit dein Zwift Click V2 optimal funktioniert, solltest du das Gerät vor jeder Trainings-Session in der Zwift-App verbinden.\nAnsonsten funktioniert der Click V2 nach einer Minute nicht mehr.\n\n1. Öffne die Zwift-App (nicht Companion!).\n2. Melde dich an (kein Abonnement nötig) und öffne den Bildschirm für die Geräteverbindung.\n3. Verbinde deinen Trainer und dann den Zwift Click V2.\n4. Schließe die Zwift-App wieder und verbinde dich erneut in BikeControl.",
|
||||
"close": "Schließen",
|
||||
"commandsRemainingToday": "{commandsRemainingToday}/{dailyCommandLimit} verbleibende Befehle heute",
|
||||
"configuration": "Konfiguration",
|
||||
@@ -66,7 +67,7 @@
|
||||
}
|
||||
},
|
||||
"connection": "Verbindung",
|
||||
"continueAction": "Weitermachen",
|
||||
"continueAction": "Weiter",
|
||||
"controlAppUsingModes": "Steuere {appName} über {modes}",
|
||||
"@controlAppUsingModes": {
|
||||
"placeholders": {
|
||||
@@ -139,6 +140,7 @@
|
||||
"duplicate": "Duplikat",
|
||||
"enableAutoRotation": "Aktiviere die automatische Drehung auf Ihrem Gerät, um sicherzustellen, dass die App ordnungsgemäß funktioniert.",
|
||||
"enableBluetooth": "Bluetooth aktivieren",
|
||||
"enableItInTheConnectionSettingsFirst": "Aktiviere es zuerst in den Verbindungseinstellungen.",
|
||||
"enableKeyboardAccessMessage": "Aktiviere im folgenden Bildschirm die Tastatursteuerung für BikeControl. Falls BikeControl nicht angezeigt wird, füge es bitte manuell hinzu.",
|
||||
"enableKeyboardMouseControl": "BikeControl sendet Maus- oder Tastaturkommandos an {appName}",
|
||||
"@enableKeyboardMouseControl": {
|
||||
@@ -148,10 +150,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"enableLocalConnectionMethodFirst": "Aktivieren Sie zuerst die Methode „Lokale Verbindung“.",
|
||||
"enableMediaKeyDetection": "Medientastenerkennung aktivieren",
|
||||
"enableMywhooshLinkInTheConnectionSettingsFirst": "Aktiviere zuerst MyWhoosh Link in den Verbindungseinstellungen.",
|
||||
"enablePairingProcess": "Kopplungsprozess aktivieren",
|
||||
"enablePermissions": "Berechtigungen aktivieren",
|
||||
"enableSteeringWithPhone": "Sensoren Ihres Telefons aktivieren z.B. zum Lenken",
|
||||
"enableSteeringWithPhone": "Lenkung über Handy-Sensoren aktivieren",
|
||||
"enableVibrationFeedback": "Vibrationsfeedback beim Gangwechsel aktivieren",
|
||||
"enableZwiftControllerBluetooth": "Zwift Controller aktivieren (Bluetooth)",
|
||||
"enableZwiftControllerNetwork": "Zwift Controller aktivieren (Netzwerk)",
|
||||
@@ -394,6 +398,7 @@
|
||||
"simulateTouch": "Berührung simulieren",
|
||||
"skip": "Überspringen",
|
||||
"stop": "Stoppen",
|
||||
"supportedActions": "Unterstützte Aktionen",
|
||||
"targetOtherDevice": "Anderes Gerät",
|
||||
"targetThisDevice": "Dieses Gerät",
|
||||
"theFollowingPermissionsRequired": "Folgende Berechtigungen sind erforderlich:",
|
||||
@@ -408,7 +413,22 @@
|
||||
"tryingToConnectAgain": "Verbinde erneut...",
|
||||
"unassignAction": "Zuweisung aufheben",
|
||||
"unlockFullVersion": "Vollversion freischalten",
|
||||
"unlockingNotPossible": "Aufgrund technischer Probleme ist eine Entsperrung derzeit nicht möglich; daher kann die App solange uneingeschränkt verwendet werden.",
|
||||
"unlock_bikecontrolAndZwiftNetwork": "BikeControl und Zwift müssen sich im selben Netzwerk befinden. Es kann einige Sekunden dauern, bis sie angezeigt werden.",
|
||||
"unlock_confirmByPressingAButtonOnYourDevice": "Bestätigen Sie durch Drücken einer Taste auf Ihrem Gerät.",
|
||||
"unlock_connectToBikecontrol": "Verbinden mit BikeControl",
|
||||
"unlock_deviceIsCurrentlyLocked": "Das Gerät ist derzeit gesperrt.",
|
||||
"unlock_isnowunlocked": "{device} ist jetzt freigeschaltet",
|
||||
"unlock_markAsUnlocked": "Als entsperrt markieren",
|
||||
"unlock_notWorking": "Funktioniert nicht?",
|
||||
"unlock_openZwift": "Öffnen Sie Zwift (nicht die Companion-App) auf diesem oder einem anderen Gerät.",
|
||||
"unlock_unlockManually": "Manuelles Entsperren",
|
||||
"unlock_unlockNow": "Jetzt freischalten",
|
||||
"unlock_unlockedUntilAroundDate": "Entsperrt bis etwa {date}",
|
||||
"unlock_waitingForZwift": "Warten auf die Entsperrung Ihres Geräts durch Zwift...",
|
||||
"unlock_youCanNowCloseZwift": "Zwift kann jetzt geschlossen werden.",
|
||||
"unlock_yourTrialPhaseHasExpired": "Testphase ist abgelaufen. Bitte erwerbe die Vollversion, um die komfortable Entsperrfunktion freizuschalten.",
|
||||
"unlock_yourZwiftClickMightBeUnlockedAlready": "Ihr Zwift Click ist möglicherweise bereits freigeschaltet.",
|
||||
"unlockingNotPossible": "Eine Freischaltung ist derzeit noch nicht möglich, daher ist die App vorerst uneingeschränkt nutzbar!",
|
||||
"update": "Aktualisieren",
|
||||
"useCustomKeymapForButton": "Verwende eine benutzerdefinierte Tastaturbelegung, um die",
|
||||
"version": "Version {version}",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"accessibilityDescription": "BikeControl needs accessibility permission to control your training apps.",
|
||||
"accessibilityDisclaimer": "BikeControl will only access your screen to perform the gestures you configure. No other accessibility features or personal information will be accessed.",
|
||||
"accessibilityReasonControl": "• To enable you to control apps like MyWhoosh, IndieVelo, and others using your Zwift devices",
|
||||
"accessibilityReasonControl": "• To enable you to control apps like MyWhoosh, TrainingPeaks, and others using your Zwift devices",
|
||||
"accessibilityReasonTouch": "• To simulate touch gestures on your screen for controlling trainer apps",
|
||||
"accessibilityReasonWindow": "• To detect which training app window is currently active",
|
||||
"accessibilityServiceExplanation": "BikeControl needs to use Android's AccessibilityService API to function properly.",
|
||||
@@ -23,6 +23,7 @@
|
||||
"allowsRunningInBackground": "Allows BikeControl to keep running in background",
|
||||
"alreadyBoughtTheApp": "Already bought the app? You don't need to pay for BikeControl, again. Due to technical difficulties, it's not possible to determine if the app was bought already.\n\nEnter your Play Store purchase ID (e.g. GPA.3356-1337-1338-1339) to unlock the full version. If you can't find it, please get in touch with me directly.",
|
||||
"alreadyBoughtTheAppPreviously": "Already bought the app previously?",
|
||||
"androidSystemAction": "Android System Action",
|
||||
"appIdActions": "{appId} actions",
|
||||
"@appIdActions": {
|
||||
"placeholders": {
|
||||
@@ -45,7 +46,7 @@
|
||||
"chooseBikeControlInConnectionScreen": "Choose BikeControl in the connection screen.",
|
||||
"clickAButtonOnYourController": "Click a button on your controller to edit its action or tap the edit icon.",
|
||||
"clickV2EventInfo": "Your Click V2 may no longer send button events. Please check by tapping a few buttons and see if they are visible in BikeControl.",
|
||||
"clickV2Instructions": "To make your Zwift Click V2 work best, you should connect it in the Zwift app before each training session.\nIf you don't do that, the Click V2 will stop working after a minute.\n\n1. Open Zwift app\n2. Log in (subscription not required) and open the device connection screen\n3. Connect your Trainer, then connect the Zwift Click V2\n4. Close the Zwift app again and connect again in BikeControl",
|
||||
"clickV2Instructions": "To make your Zwift Click V2 work best, you should connect it in the Zwift app before each training session.\nIf you don't do that, the Click V2 will stop working after a minute.\n\n1. Open Zwift app (not the Companion!)\n2. Log in (subscription not required) and open the device connection screen\n3. Connect your Trainer, then connect the Zwift Click V2\n4. Close the Zwift app again and connect again in BikeControl",
|
||||
"close": "Close",
|
||||
"commandsRemainingToday": "{commandsRemainingToday}/{dailyCommandLimit} commands remaining today",
|
||||
"configuration": "Configuration",
|
||||
@@ -139,6 +140,7 @@
|
||||
"duplicate": "Duplicate",
|
||||
"enableAutoRotation": "Enable auto-rotation on your device to make sure the app works correctly.",
|
||||
"enableBluetooth": "Enable Bluetooth",
|
||||
"enableItInTheConnectionSettingsFirst": "Enable it in the connection settings first.",
|
||||
"enableKeyboardAccessMessage": "Enable keyboard access in the following screen for BikeControl. If you don't see BikeControl, please add it manually.",
|
||||
"enableKeyboardMouseControl": "BikeControl will send mouse or keyboard actions to control {appName}.",
|
||||
"@enableKeyboardMouseControl": {
|
||||
@@ -148,10 +150,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"enableLocalConnectionMethodFirst": "Enable Local Connection method, first.",
|
||||
"enableMediaKeyDetection": "Enable Media Key Detection",
|
||||
"enableMywhooshLinkInTheConnectionSettingsFirst": "Enable MyWhoosh Link in the connection settings first.",
|
||||
"enablePairingProcess": "Enable Pairing Process",
|
||||
"enablePermissions": "Enable Permissions",
|
||||
"enableSteeringWithPhone": "Enable Phones' sensors to enable e.g. steering",
|
||||
"enableSteeringWithPhone": "Enable Steering with your phone's sensors",
|
||||
"enableVibrationFeedback": "Enable vibration feedback when shifting gears",
|
||||
"enableZwiftControllerBluetooth": "Enable Zwift Controller (Bluetooth)",
|
||||
"enableZwiftControllerNetwork": "Enable Zwift Controller (Network)",
|
||||
@@ -394,6 +398,7 @@
|
||||
"simulateTouch": "Simulate Touch",
|
||||
"skip": "Skip",
|
||||
"stop": "Stop",
|
||||
"supportedActions": "Supported Actions",
|
||||
"targetOtherDevice": "Other Device",
|
||||
"targetThisDevice": "This Device",
|
||||
"theFollowingPermissionsRequired": "The following permissions are required:",
|
||||
@@ -408,7 +413,22 @@
|
||||
"tryingToConnectAgain": "Trying to connect again...",
|
||||
"unassignAction": "Unassign action",
|
||||
"unlockFullVersion": "Unlock Full Version",
|
||||
"unlockingNotPossible": "Due to technical issues unlocking is currently not possible, so enjoy unlimited usage for the time being!",
|
||||
"unlock_bikecontrolAndZwiftNetwork": "BikeControl and Zwift need to be on the same network. It may take a few seconds to appear.",
|
||||
"unlock_confirmByPressingAButtonOnYourDevice": "Confirm by pressing a button on your device.",
|
||||
"unlock_connectToBikecontrol": "Connect to BikeControl",
|
||||
"unlock_deviceIsCurrentlyLocked": "Device is currently locked",
|
||||
"unlock_isnowunlocked": "{device} is now unlocked",
|
||||
"unlock_markAsUnlocked": "Mark as Unlocked",
|
||||
"unlock_notWorking": "Not working?",
|
||||
"unlock_openZwift": "Open Zwift (not the Companion) on this or another device",
|
||||
"unlock_unlockManually": "Unlock manually",
|
||||
"unlock_unlockNow": "Unlock now",
|
||||
"unlock_unlockedUntilAroundDate": "Unlocked until around {date}",
|
||||
"unlock_waitingForZwift": "Waiting for Zwift to unlock your device...",
|
||||
"unlock_youCanNowCloseZwift": "You can now close Zwift and return to BikeControl.",
|
||||
"unlock_yourTrialPhaseHasExpired": "Your trial phase has expired. Please purchase the full version to unlock the comfortable unlocking feature :)",
|
||||
"unlock_yourZwiftClickMightBeUnlockedAlready": "Your Zwift Click might be unlocked already.",
|
||||
"unlockingNotPossible": "Unlocking is currently not yet possible, so enjoy unlimited usage for the time being!",
|
||||
"update": "Update",
|
||||
"useCustomKeymapForButton": "Use a custom keymap to support the",
|
||||
"version": "Version {version}",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"accessibilityDescription": "BikeControl a besoin d'une autorisation d'accessibilité pour contrôler vos applications d'entraînement.",
|
||||
"accessibilityDisclaimer": "BikeControl n'accédera à votre écran que pour effectuer les gestes que vous aurez configurés. Aucune autre fonctionnalité d'accessibilité ni aucune autre information personnelle ne sera accessible.",
|
||||
"accessibilityReasonControl": "• Pour vous permettre de contrôler des applications telles que MyWhoosh, IndieVelo et d'autres à l'aide de vos appareils Zwift.",
|
||||
"accessibilityReasonControl": "• Pour vous permettre de contrôler des applications telles que MyWhoosh, TrainingPeaks et d'autres à l'aide de vos appareils Zwift.",
|
||||
"accessibilityReasonTouch": "• Pour simuler des gestes tactiles sur votre écran afin de contrôler les applications d'entraînement",
|
||||
"accessibilityReasonWindow": "• Pour détecter quelle fenêtre de l'application d'entraînement est actuellement active",
|
||||
"accessibilityServiceExplanation": "BikeControl doit utiliser l'API AccessibilityService d'Android pour fonctionner correctement.",
|
||||
@@ -23,6 +23,7 @@
|
||||
"allowsRunningInBackground": "Permet à BikeControl de continuer à fonctionner en arrière-plan",
|
||||
"alreadyBoughtTheApp": "Vous avez déjà acheté l'application ? Vous n'avez pas besoin de payer BikeControl une seconde fois. En raison de difficultés techniques, il est impossible de déterminer si l'application a déjà été achetée. \n\nSaisissez votre identifiant d'achat Play Store (par exemple : GPA.3356-1337-1338-1339) pour débloquer la version complète. Si vous ne le trouvez pas, veuillez me contacter directement.",
|
||||
"alreadyBoughtTheAppPreviously": "Vous avez déjà acheté l'application ?",
|
||||
"androidSystemAction": "Actions système Android",
|
||||
"appIdActions": "{appId} actions",
|
||||
"@appIdActions": {
|
||||
"placeholders": {
|
||||
@@ -45,7 +46,7 @@
|
||||
"chooseBikeControlInConnectionScreen": "Sélectionnez BikeControl dans l'écran de connexion.",
|
||||
"clickAButtonOnYourController": "Cliquez sur un bouton de votre manette pour modifier son action ou appuyez sur l'icône de modification.",
|
||||
"clickV2EventInfo": "Votre Click V2 n'envoie peut-être plus les événements liés aux boutons. Vérifiez en appuyant sur quelques boutons et voyez s'ils apparaissent dans BikeControl.",
|
||||
"clickV2Instructions": "Pour que ton Zwift Click V2 marche super bien, tu dois le connecter à l'appli Zwift une fois par jour.\nSi tu ne le fais pas, le Click V2 s'arrêtera de fonctionner au bout d'une minute.\n\n1. Ouvre l'appli Zwift.\n2. Connecte-toi (pas besoin d'abonnement) et ouvre l'écran de connexion des appareils.\n3. Connecte ton home trainer, puis connecte le Zwift Click V2.\n4. Ferme l'appli Zwift et reconnecte-toi dans BikeControl.",
|
||||
"clickV2Instructions": "Pour que ton Zwift Click V2 marche super bien, tu dois le connecter à l'appli Zwift une fois par jour.\nSi tu ne le fais pas, le Click V2 s'arrêtera de fonctionner au bout d'une minute.\n\n1. Ouvre l'appli Zwift (pas Companion!)\n2. Connecte-toi (pas besoin d'abonnement) et ouvre l'écran de connexion des appareils.\n3. Connecte ton home trainer, puis connecte le Zwift Click V2.\n4. Ferme l'appli Zwift et reconnecte-toi dans BikeControl.",
|
||||
"close": "Fermer",
|
||||
"commandsRemainingToday": "{commandsRemainingToday}/{dailyCommandLimit} commandes restantes aujourd'hui",
|
||||
"configuration": "Configuration",
|
||||
@@ -139,6 +140,7 @@
|
||||
"duplicate": "Double",
|
||||
"enableAutoRotation": "Activez la rotation automatique sur votre appareil pour vous assurer que l'application fonctionne correctement.",
|
||||
"enableBluetooth": "Activer le Bluetooth",
|
||||
"enableItInTheConnectionSettingsFirst": "Activez-le d'abord dans les paramètres de connexion.",
|
||||
"enableKeyboardAccessMessage": "Activez l'accès au clavier dans l'écran suivant pour BikeControl. Si vous ne voyez pas BikeControl, veuillez l'ajouter manuellement.",
|
||||
"enableKeyboardMouseControl": "BikeControl enverra des actions de souris ou de clavier pour contrôler {appName} .",
|
||||
"@enableKeyboardMouseControl": {
|
||||
@@ -148,10 +150,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"enableLocalConnectionMethodFirst": "Activez d'abord le mode de connexion locale.",
|
||||
"enableMediaKeyDetection": "Activer la détection des touches multimédias",
|
||||
"enableMywhooshLinkInTheConnectionSettingsFirst": "Activez d'abord MyWhoosh Link dans les paramètres de connexion.",
|
||||
"enablePairingProcess": "Activer le processus d'appairage",
|
||||
"enablePermissions": "Activer les autorisations",
|
||||
"enableSteeringWithPhone": "Activez les capteurs du téléphone pour permettre, par exemple, la direction.",
|
||||
"enableSteeringWithPhone": "Activez la direction avec les capteurs de votre téléphone",
|
||||
"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)",
|
||||
@@ -394,6 +398,7 @@
|
||||
"simulateTouch": "Simuler le toucher",
|
||||
"skip": "Sauter",
|
||||
"stop": "Arrêt",
|
||||
"supportedActions": "Actions prises en charge",
|
||||
"targetOtherDevice": "Autre appareil",
|
||||
"targetThisDevice": "Cet appareil",
|
||||
"theFollowingPermissionsRequired": "Les autorisations suivantes sont requises :",
|
||||
@@ -408,7 +413,22 @@
|
||||
"tryingToConnectAgain": "Tentative de reconnexion...",
|
||||
"unassignAction": "Action de désaffectation",
|
||||
"unlockFullVersion": "Débloquer la version complète",
|
||||
"unlockingNotPossible": "En raison de problèmes techniques, le déverrouillage est actuellement impossible. Profitez donc d'une utilisation illimitée pour le moment !",
|
||||
"unlock_bikecontrolAndZwiftNetwork": "BikeControl et Zwift doivent être connectés au même réseau. L'affichage peut prendre quelques secondes.",
|
||||
"unlock_confirmByPressingAButtonOnYourDevice": "Confirmez en appuyant sur un bouton de votre appareil.",
|
||||
"unlock_connectToBikecontrol": "Se connecter à ",
|
||||
"unlock_deviceIsCurrentlyLocked": "L'appareil est actuellement verrouillé.",
|
||||
"unlock_isnowunlocked": "{device} est maintenant déverrouillé",
|
||||
"unlock_markAsUnlocked": "Marquer comme déverrouillé",
|
||||
"unlock_notWorking": "Ça ne fonctionne pas ?",
|
||||
"unlock_openZwift": "Ouvrez Zwift (et non l'application Companion) sur cet appareil ou un autre.",
|
||||
"unlock_unlockManually": "Déverrouiller manuellement",
|
||||
"unlock_unlockNow": "Déverrouiller maintenant",
|
||||
"unlock_unlockedUntilAroundDate": "Déverrouillé jusqu'à environ {date}",
|
||||
"unlock_waitingForZwift": "En attente du déverrouillage de votre appareil par Zwift...",
|
||||
"unlock_youCanNowCloseZwift": "Vous pouvez maintenant fermer Zwift et revenir à BikeControl.",
|
||||
"unlock_yourTrialPhaseHasExpired": "Votre période d'essai est terminée. Veuillez acheter la version complète pour débloquer la fonction de déverrouillage simplifiée :)",
|
||||
"unlock_yourZwiftClickMightBeUnlockedAlready": "Votre Zwift Click est peut-être déjà déverrouillé.",
|
||||
"unlockingNotPossible": "Le déverrouillage n'est pas encore possible pour le moment, alors profitez d'une utilisation illimitée pour l'instant !",
|
||||
"update": "Mise à jour",
|
||||
"useCustomKeymapForButton": "Utilisez une configuration de touches personnalisée pour prendre en charge",
|
||||
"version": "Version {version}",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"accessibilityDescription": "BikeControl necessita dell'autorizzazione di accessibilità per controllare le tue app di allenamento.",
|
||||
"accessibilityDisclaimer": "BikeControl accederà al tuo schermo solo per eseguire i gesti da te configurati. Non accederà ad altre funzioni di accessibilità o a informazioni personali.",
|
||||
"accessibilityReasonControl": "• Per consentirti di controllare app come MyWhoosh, IndieVelo e altre utilizzando i tuoi dispositivi Zwift",
|
||||
"accessibilityReasonControl": "• Per consentirti di controllare app come MyWhoosh, TrainingPeaks e altre utilizzando i tuoi dispositivi Zwift",
|
||||
"accessibilityReasonTouch": "• Per simulare i gesti touch sullo schermo per controllare le app di allenamento",
|
||||
"accessibilityReasonWindow": "• Per rilevare quale finestra dell'app di allenamento è attualmente attiva",
|
||||
"accessibilityServiceExplanation": "Per funzionare correttamente, BikeControl deve utilizzare l'API AccessibilityService di Android.",
|
||||
@@ -14,7 +14,7 @@
|
||||
"action": "Azione",
|
||||
"adjustControllerButtons": "Regola i pulsanti del controller",
|
||||
"afterDate": "Dopo il {date}",
|
||||
"allow": "Consentire",
|
||||
"allow": "Consenti",
|
||||
"allowAccessibilityService": "Consenti servizio di accessibilità",
|
||||
"allowBluetoothConnections": "Consenti connessioni Bluetooth",
|
||||
"allowBluetoothScan": "Consenti scansione Bluetooth",
|
||||
@@ -23,6 +23,7 @@
|
||||
"allowsRunningInBackground": "Consente a BikeControl di continuare a funzionare in background",
|
||||
"alreadyBoughtTheApp": "Hai già acquistato l'app? Non devi più pagare per BikeControl. A causa di problemi tecnici, non è possibile determinare se l'app è già stata acquistata.\n\nInserisci il tuo ID di acquisto sul Play Store (ad esempio GPA.3356-1337-1338-1339) per sbloccare la versione completa. Se non riesci a trovarlo, contattami direttamente.",
|
||||
"alreadyBoughtTheAppPreviously": "Hai già acquistato l'app in precedenza?",
|
||||
"androidSystemAction": "Azione del sistema Android",
|
||||
"appIdActions": "{appId} azioni",
|
||||
"@appIdActions": {
|
||||
"placeholders": {
|
||||
@@ -45,7 +46,7 @@
|
||||
"chooseBikeControlInConnectionScreen": "Selezionare BikeControl nella schermata di connessione.",
|
||||
"clickAButtonOnYourController": "Fai clic su un pulsante del controller per modificarne l'azione oppure tocca l'icona di modifica.",
|
||||
"clickV2EventInfo": "Il tuo Click V2 potrebbe non inviare più eventi tramite i pulsanti. Verifica toccando alcuni pulsanti e verifica se sono visibili in BikeControl.",
|
||||
"clickV2Instructions": "Per far funzionare al meglio il tuo Zwift Click V2, dovresti collegarlo all'app Zwift prima di ogni sessione di allenamento. In caso contrario, il Click V2 smetterà di funzionare dopo un minuto. \n1. Apri l'app Zwift \n2. Accedi (non richiesto un abbonamento attivo) e apri la schermata di connessione del dispositivo \n3. Collega il tuo trainer, quindi collega lo Zwift Click V2, verifica che i comandi del controller funzionino \n4. Chiudi nuovamente l'app Zwift e riconnettiti a BikeControl",
|
||||
"clickV2Instructions": "Per far funzionare al meglio il tuo Zwift Click V2, dovresti collegarlo all'app Zwift prima di ogni sessione di allenamento. In caso contrario, il Click V2 smetterà di funzionare dopo un minuto. \n\n1. Apri l'app Zwift (non il Companion!)\n2. Accedi (non richiesto un abbonamento attivo) e apri la schermata di connessione del dispositivo \n3. Collega il tuo trainer, quindi collega lo Zwift Click V2, verifica che i comandi del controller funzionino \n4. Chiudi nuovamente l'app Zwift e riconnettiti a BikeControl",
|
||||
"close": "Chiudi",
|
||||
"commandsRemainingToday": "{commandsRemainingToday}/{dailyCommandLimit} comandi rimanenti oggi",
|
||||
"configuration": "Configurazione",
|
||||
@@ -139,6 +140,7 @@
|
||||
"duplicate": "Duplica",
|
||||
"enableAutoRotation": "Abilita la rotazione automatica sul tuo dispositivo per assicurarti che l'app funzioni correttamente.",
|
||||
"enableBluetooth": "Abilita Bluetooth",
|
||||
"enableItInTheConnectionSettingsFirst": "Per prima cosa abilitalo nelle impostazioni di connessione.",
|
||||
"enableKeyboardAccessMessage": "Abilita l'accesso tramite tastiera nella schermata seguente per BikeControl. Se BikeControl non è presente, aggiungilo manualmente.",
|
||||
"enableKeyboardMouseControl": "BikeControl invierà azioni del mouse o della tastiera per controllare {appName} .",
|
||||
"@enableKeyboardMouseControl": {
|
||||
@@ -148,10 +150,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"enableLocalConnectionMethodFirst": "Abilitare prima il metodo di connessione locale.",
|
||||
"enableMediaKeyDetection": "Abilita rilevamento tasti multimediali",
|
||||
"enableMywhooshLinkInTheConnectionSettingsFirst": "Per prima cosa, abilita MyWhoosh Link nelle impostazioni di connessione.",
|
||||
"enablePairingProcess": "Abilita processo di associazione",
|
||||
"enablePermissions": "Abilita autorizzazioni",
|
||||
"enableSteeringWithPhone": "Abilita i sensori dei telefoni per abilitare ad esempio lo sterzo",
|
||||
"enableSteeringWithPhone": "Abilita lo sterzo con i sensori del tuo telefono",
|
||||
"enableVibrationFeedback": "Abilita il feedback delle vibrazioni durante il cambio marcia",
|
||||
"enableZwiftControllerBluetooth": "Abilita il controller Zwift (Bluetooth)",
|
||||
"enableZwiftControllerNetwork": "Abilita Zwift Controller (rete)",
|
||||
@@ -394,6 +398,7 @@
|
||||
"simulateTouch": "Simula il tocco",
|
||||
"skip": "Saltare",
|
||||
"stop": "Stop",
|
||||
"supportedActions": "Azioni supportate",
|
||||
"targetOtherDevice": "Altro dispositivo",
|
||||
"targetThisDevice": "Questo dispositivo",
|
||||
"theFollowingPermissionsRequired": "Sono richieste le seguenti autorizzazioni:",
|
||||
@@ -408,7 +413,22 @@
|
||||
"tryingToConnectAgain": "Sto provando a connettermi di nuovo...",
|
||||
"unassignAction": "Annulla assegnazione azione",
|
||||
"unlockFullVersion": "Sblocca la versione completa",
|
||||
"unlockingNotPossible": "A causa di problemi tecnici, al momento non è possibile sbloccare il tutto, ma per il momento puoi usufruire di un utilizzo illimitato!",
|
||||
"unlock_bikecontrolAndZwiftNetwork": "BikeControl e Zwift devono essere sulla stessa rete. Potrebbero essere necessari alcuni secondi per visualizzarli.",
|
||||
"unlock_confirmByPressingAButtonOnYourDevice": "Conferma premendo un pulsante sul tuo dispositivo.",
|
||||
"unlock_connectToBikecontrol": "Connettiti a ",
|
||||
"unlock_deviceIsCurrentlyLocked": "Il dispositivo è attualmente bloccato",
|
||||
"unlock_isnowunlocked": "{device} è ora sbloccato",
|
||||
"unlock_markAsUnlocked": "Segna come sbloccato",
|
||||
"unlock_notWorking": "Non funziona?",
|
||||
"unlock_openZwift": "Apri Zwift (non il Companion) su questo o un altro dispositivo",
|
||||
"unlock_unlockManually": "Sblocca manualmente",
|
||||
"unlock_unlockNow": "Sblocca ora",
|
||||
"unlock_unlockedUntilAroundDate": "Sbloccato fino a circa {date}",
|
||||
"unlock_waitingForZwift": "In attesa che Zwift sblocchi il tuo dispositivo...",
|
||||
"unlock_youCanNowCloseZwift": "Ora puoi chiudere Zwift e tornare a BikeControl.",
|
||||
"unlock_yourTrialPhaseHasExpired": "La tua fase di prova è scaduta. Acquista la versione completa per sbloccare la comoda funzione di sblocco :)",
|
||||
"unlock_yourZwiftClickMightBeUnlockedAlready": "Il tuo Zwift Click potrebbe essere già sbloccato.",
|
||||
"unlockingNotPossible": "Al momento non è ancora possibile sbloccarlo, quindi per il momento goditi un utilizzo illimitato!",
|
||||
"update": "Aggiorna",
|
||||
"useCustomKeymapForButton": "Utilizzare una mappa dei tasti personalizzata per supportare",
|
||||
"version": "Versione{version}",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"accessibilityDescription": "BikeControl potrzebuje uprawnień dostępu, aby móc sterować aplikacjami treningowymi.",
|
||||
"accessibilityDisclaimer": "BikeControl będzie miał dostęp do Twojego ekranu wyłącznie w celu wykonania skonfigurowanych przez Ciebie gestów. Nie będzie miał dostępu do żadnych innych funkcji ułatwień dostępu ani danych osobowych.",
|
||||
"accessibilityReasonControl": "• Aby umożliwić Ci sterowanie aplikacjami takimi jak MyWhoosh, IndieVelo i innymi za pomocą urządzeń Zwift",
|
||||
"accessibilityReasonControl": "• Aby umożliwić Ci sterowanie aplikacjami takimi jak MyWhoosh, TrainingPeaks i innymi za pomocą urządzeń Zwift",
|
||||
"accessibilityReasonTouch": "• Aby symulować gesty dotykowe na ekranie w celu sterowania aplikacją treningową",
|
||||
"accessibilityReasonWindow": "• Aby wykryć, które okno aplikacji treningowej jest aktualnie aktywne",
|
||||
"accessibilityServiceExplanation": "Aby działać prawidłowo, BikeControl musi korzystać z interfejsu API AccessibilityService systemu Android.",
|
||||
@@ -23,6 +23,7 @@
|
||||
"allowsRunningInBackground": "Umożliwia działanie BikeControl w tle",
|
||||
"alreadyBoughtTheApp": "Kupiłeś już aplikację? Nie musisz płacić za BikeControl. Z powodu problemów technicznych nie można ustalić, czy aplikacja została już zakupiona. \n\nWprowadź swój identyfikator zakupu w Sklepie Play (np. GPA.3356-1337-1338-1339), aby odblokować pełną wersję. Jeśli nie możesz jej znaleźć, skontaktuj się ze mną bezpośrednio.",
|
||||
"alreadyBoughtTheAppPreviously": "Już wcześniej kupiłeś aplikację?",
|
||||
"androidSystemAction": "Akcja systemu Android",
|
||||
"appIdActions": "{appId} działania",
|
||||
"@appIdActions": {
|
||||
"placeholders": {
|
||||
@@ -45,7 +46,7 @@
|
||||
"chooseBikeControlInConnectionScreen": "Wybierz BikeControl na ekranie połączenia.",
|
||||
"clickAButtonOnYourController": "Kliknij przycisk na kontrolerze, aby edytować jego akcję lub naciśnij ikonę edycji.",
|
||||
"clickV2EventInfo": "Twóje Click V2 może już nie wysyłać zdarzeń przycisków. Sprawdź, naciskając kilka przycisków, czy są one widoczne w BikeControl.",
|
||||
"clickV2Instructions": "Aby Twoje Zwift Click V2 działały optymalnie, należy połączyć je z aplikacją Zwift przed każdym treningiem.\nJeśli tego nie zrobisz, Click V2 przestanie działać po minucie.\n\n1. Otwórz aplikację Zwift\n2. Zaloguj się (nie wymaga subskrypcji) i otwórz ekran połączeń z urządzeniami\n3. Połącz się z trenażerem, a następnie z Zwift Click V2\n4. Zamknij aplikację Zwift i ponownie połącz się z BikeControl",
|
||||
"clickV2Instructions": "Aby Twoje Zwift Click V2 działały optymalnie, należy połączyć je z aplikacją Zwift przed każdym treningiem.\nJeśli tego nie zrobisz, Click V2 przestanie działać po minucie.\n\n1. Otwórz aplikację Zwift (nie Companion!)\n2. Zaloguj się (nie wymaga subskrypcji) i otwórz ekran połączeń z urządzeniami\n3. Połącz się z trenażerem, a następnie z Zwift Click V2\n4. Zamknij aplikację Zwift i ponownie połącz się z BikeControl",
|
||||
"close": "Zamknij",
|
||||
"commandsRemainingToday": "{commandsRemainingToday}/{dailyCommandLimit} dzisiejsze pozostałe polecenia",
|
||||
"configuration": "Konfiguracja",
|
||||
@@ -139,6 +140,7 @@
|
||||
"duplicate": "Duplikuj",
|
||||
"enableAutoRotation": "Włącz funkcję automatycznego obracania na urządzeniu, aby mieć pewność, że aplikacja będzie działać prawidłowo.",
|
||||
"enableBluetooth": "Włącz Bluetooth",
|
||||
"enableItInTheConnectionSettingsFirst": "Najpierw włącz tę opcję w ustawieniach połączenia.",
|
||||
"enableKeyboardAccessMessage": "Włącz dostęp do klawiatury dla BikeControl na poniższym ekranie. Jeśli nie widzisz BikeControl, dodaj go ręcznie.",
|
||||
"enableKeyboardMouseControl": "BikeControl będzie wysyłał akcje myszy lub klawiatury do kontrolera {appName} .",
|
||||
"@enableKeyboardMouseControl": {
|
||||
@@ -148,10 +150,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"enableLocalConnectionMethodFirst": "Najpierw włącz metodę połączenia lokalnego.",
|
||||
"enableMediaKeyDetection": "Włącz rozpoznawanie klawiszy multimedialnych",
|
||||
"enableMywhooshLinkInTheConnectionSettingsFirst": "Najpierw włącz MyWhoosh Link w ustawieniach połączenia.",
|
||||
"enablePairingProcess": "Włącz proces parowania",
|
||||
"enablePermissions": "Nadaj uprawnienia",
|
||||
"enableSteeringWithPhone": "Włącz czujniki telefonów, aby umożliwić np. sterowanie",
|
||||
"enableSteeringWithPhone": "Włącz sterowanie za pomocą czujników telefonu",
|
||||
"enableVibrationFeedback": "Włącz wibracje podczas zmiany biegów",
|
||||
"enableZwiftControllerBluetooth": "Włącz kontroler Zwift (Bluetooth)",
|
||||
"enableZwiftControllerNetwork": "Włącz kontroler Zwift (sieć)",
|
||||
@@ -394,6 +398,7 @@
|
||||
"simulateTouch": "Symuluj dotyk",
|
||||
"skip": "Pominąć",
|
||||
"stop": "Stop",
|
||||
"supportedActions": "Obsługiwane działania",
|
||||
"targetOtherDevice": "Inne urządzenie",
|
||||
"targetThisDevice": "To urządzenie",
|
||||
"theFollowingPermissionsRequired": "Wymagane są następujące uprawnienia:",
|
||||
@@ -408,7 +413,22 @@
|
||||
"tryingToConnectAgain": "Próba ponownego połączenia...",
|
||||
"unassignAction": "Anuluj przypisanie akcji",
|
||||
"unlockFullVersion": "Odblokuj pełną wersję",
|
||||
"unlockingNotPossible": "Z uwagi na problemy techniczne odblokowanie nie jest obecnie możliwe, więc ciesz się nieograniczonym użytkowaniem!",
|
||||
"unlock_bikecontrolAndZwiftNetwork": "BikeControl i Zwift muszą być w tej samej sieci. Pojawienie się aplikacji może potrwać kilka sekund.",
|
||||
"unlock_confirmByPressingAButtonOnYourDevice": "Potwierdź, naciskając przycisk na swoim urządzeniu.",
|
||||
"unlock_connectToBikecontrol": "Połącz się z BikeControl",
|
||||
"unlock_deviceIsCurrentlyLocked": "Urządzenie jest obecnie zablokowane",
|
||||
"unlock_isnowunlocked": "{device} jest teraz odblokowany",
|
||||
"unlock_markAsUnlocked": "Oznacz jako odblokowane",
|
||||
"unlock_notWorking": "Nie działa?",
|
||||
"unlock_openZwift": "Otwórz aplikację Zwift (nie Companion) na tym lub innym urządzeniu",
|
||||
"unlock_unlockManually": "Odblokuj ręcznie",
|
||||
"unlock_unlockNow": "Odblokuj teraz",
|
||||
"unlock_unlockedUntilAroundDate": "Odblokowane do około {date}",
|
||||
"unlock_waitingForZwift": "Czekamy, aż Zwift odblokuje Twoje urządzenie...",
|
||||
"unlock_youCanNowCloseZwift": "Możesz teraz zamknąć aplikację Zwift i wrócić do BikeControl.",
|
||||
"unlock_yourTrialPhaseHasExpired": "Twój okres próbny wygasł. Kup pełną wersję, aby odblokować wygodną funkcję odblokowywania :)",
|
||||
"unlock_yourZwiftClickMightBeUnlockedAlready": "Twoje urządzenie Zwift Click może być już odblokowane.",
|
||||
"unlockingNotPossible": "Odblokowanie nie jest obecnie możliwe, więc ciesz się nieograniczonym użytkowaniem!",
|
||||
"update": "Aktualizacja",
|
||||
"useCustomKeymapForButton": "Użyj niestandardowej mapy klawiszy, aby obsługiwać",
|
||||
"version": "Wersja {version}",
|
||||
|
||||
@@ -109,14 +109,16 @@ Future<void> _persistCrash({
|
||||
..writeln();
|
||||
|
||||
final directory = await _getLogDirectory();
|
||||
final file = File('${directory.path}/app.logs');
|
||||
final fileLength = await file.length();
|
||||
if (fileLength > 5 * 1024 * 1024) {
|
||||
// If log file exceeds 5MB, truncate it
|
||||
final lines = await file.readAsLines();
|
||||
final half = lines.length ~/ 2;
|
||||
final truncatedLines = lines.sublist(half);
|
||||
await file.writeAsString(truncatedLines.join('\n'));
|
||||
final file = File('${directory.path}/app.log');
|
||||
if (file.existsSync()) {
|
||||
final fileLength = await file.length();
|
||||
if (fileLength > 5 * 1024 * 1024) {
|
||||
// If log file exceeds 5MB, truncate it
|
||||
final lines = await file.readAsLines();
|
||||
final half = lines.length ~/ 2;
|
||||
final truncatedLines = lines.sublist(half);
|
||||
await file.writeAsString(truncatedLines.join('\n'));
|
||||
}
|
||||
}
|
||||
|
||||
await file.writeAsString(crashData.toString(), mode: FileMode.append);
|
||||
@@ -183,8 +185,8 @@ class _BikeControlAppState extends State<BikeControlApp> {
|
||||
return ShadcnApp(
|
||||
navigatorKey: navigatorKey,
|
||||
debugShowCheckedModeBanner: false,
|
||||
menuHandler: PopoverOverlayHandler(),
|
||||
popoverHandler: PopoverOverlayHandler(),
|
||||
menuHandler: OverlayHandler.popover,
|
||||
popoverHandler: OverlayHandler.popover,
|
||||
localizationsDelegates: [
|
||||
...ShadcnLocalizations.localizationsDelegates,
|
||||
OtherLocalizationsDelegate(),
|
||||
@@ -225,7 +227,11 @@ class _BikeControlAppState extends State<BikeControlApp> {
|
||||
? OnboardingPage(
|
||||
onComplete: () {
|
||||
setState(() {
|
||||
_showPage = BCPage.trainer;
|
||||
if (core.obpMdnsEmulator.connectedApp.value == null) {
|
||||
_showPage = BCPage.trainer;
|
||||
} else {
|
||||
_showPage = BCPage.devices;
|
||||
}
|
||||
});
|
||||
},
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/main.dart';
|
||||
import 'package:bike_control/pages/touch_area.dart';
|
||||
import 'package:bike_control/utils/actions/android.dart';
|
||||
import 'package:bike_control/utils/actions/base_actions.dart';
|
||||
@@ -14,6 +15,7 @@ import 'package:bike_control/widgets/custom_keymap_selector.dart';
|
||||
import 'package:bike_control/widgets/ui/button_widget.dart';
|
||||
import 'package:bike_control/widgets/ui/colored_title.dart';
|
||||
import 'package:bike_control/widgets/ui/colors.dart';
|
||||
import 'package:bike_control/widgets/ui/toast.dart';
|
||||
import 'package:bike_control/widgets/ui/warning.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -109,7 +111,6 @@ class _ButtonEditPageState extends State<ButtonEditPage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Text('Editing').h3,
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 600),
|
||||
curve: Curves.easeOut,
|
||||
@@ -154,15 +155,31 @@ class _ButtonEditPageState extends State<ButtonEditPage> {
|
||||
..._buildTrainerConnectionActions(core.logic.obpConnectedApp!.supportedActions),
|
||||
],
|
||||
|
||||
if (core.settings.getMyWhooshLinkEnabled() && core.logic.showMyWhooshLink) ...[
|
||||
if (core.logic.showMyWhooshLink) ...[
|
||||
SizedBox(height: 8),
|
||||
ColoredTitle(text: context.i18n.myWhooshDirectConnectAction),
|
||||
..._buildTrainerConnectionActions(core.whooshLink.supportedActions),
|
||||
if (!core.settings.getMyWhooshLinkEnabled())
|
||||
Warning(
|
||||
important: false,
|
||||
children: [
|
||||
Text(AppLocalizations.of(context).enableMywhooshLinkInTheConnectionSettingsFirst),
|
||||
],
|
||||
)
|
||||
else
|
||||
..._buildTrainerConnectionActions(core.whooshLink.supportedActions),
|
||||
],
|
||||
if (core.logic.isZwiftBleEnabled || core.logic.isZwiftMdnsEnabled) ...[
|
||||
if (core.logic.showZwiftBleEmulator || core.logic.showZwiftMsdnEmulator) ...[
|
||||
SizedBox(height: 8),
|
||||
ColoredTitle(text: context.i18n.zwiftControllerAction),
|
||||
..._buildTrainerConnectionActions(core.zwiftEmulator.supportedActions),
|
||||
if (!core.settings.getZwiftBleEmulatorEnabled() && !core.settings.getZwiftMdnsEmulatorEnabled())
|
||||
Warning(
|
||||
important: false,
|
||||
children: [
|
||||
Text(AppLocalizations.of(context).enableItInTheConnectionSettingsFirst),
|
||||
],
|
||||
)
|
||||
else
|
||||
..._buildTrainerConnectionActions(core.zwiftEmulator.supportedActions),
|
||||
],
|
||||
|
||||
if (core.logic.showLocalRemoteOptions) ...[
|
||||
@@ -175,40 +192,48 @@ class _ButtonEditPageState extends State<ButtonEditPage> {
|
||||
title: Text(context.i18n.predefinedAction(trainerApp.name)),
|
||||
isActive: false,
|
||||
onPressed: () {
|
||||
showDropdown(
|
||||
context: context,
|
||||
builder: (c) => DropdownMenu(
|
||||
children: actionsWithInGameAction!.map((keyPairAction) {
|
||||
return MenuButton(
|
||||
leading: keyPairAction.inGameAction?.icon != null
|
||||
? Icon(keyPairAction.inGameAction!.icon)
|
||||
: null,
|
||||
onPressed: (_) {
|
||||
// Copy all properties from the selected predefined action
|
||||
if (core.actionHandler.supportedModes.contains(SupportedMode.keyboard)) {
|
||||
_keyPair.physicalKey = keyPairAction.physicalKey;
|
||||
_keyPair.logicalKey = keyPairAction.logicalKey;
|
||||
_keyPair.modifiers = List.of(keyPairAction.modifiers);
|
||||
} else {
|
||||
_keyPair.physicalKey = null;
|
||||
_keyPair.logicalKey = null;
|
||||
_keyPair.modifiers = [];
|
||||
}
|
||||
if (core.actionHandler.supportedModes.contains(SupportedMode.touch)) {
|
||||
_keyPair.touchPosition = keyPairAction.touchPosition;
|
||||
} else {
|
||||
_keyPair.touchPosition = Offset.zero;
|
||||
}
|
||||
_keyPair.isLongPress = keyPairAction.isLongPress;
|
||||
_keyPair.inGameAction = keyPairAction.inGameAction;
|
||||
_keyPair.inGameActionValue = keyPairAction.inGameActionValue;
|
||||
setState(() {});
|
||||
},
|
||||
child: Text(keyPairAction.toString()),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
if (!core.settings.getLocalEnabled()) {
|
||||
buildToast(
|
||||
navigatorKey.currentContext!,
|
||||
title: AppLocalizations.of(context).enableLocalConnectionMethodFirst,
|
||||
);
|
||||
} else {
|
||||
showDropdown(
|
||||
context: context,
|
||||
builder: (c) => DropdownMenu(
|
||||
children: actionsWithInGameAction!.map((keyPairAction) {
|
||||
return MenuButton(
|
||||
leading: keyPairAction.inGameAction?.icon != null
|
||||
? Icon(keyPairAction.inGameAction!.icon)
|
||||
: null,
|
||||
onPressed: (_) {
|
||||
// Copy all properties from the selected predefined action
|
||||
if (core.actionHandler.supportedModes.contains(SupportedMode.keyboard)) {
|
||||
_keyPair.physicalKey = keyPairAction.physicalKey;
|
||||
_keyPair.logicalKey = keyPairAction.logicalKey;
|
||||
_keyPair.modifiers = List.of(keyPairAction.modifiers);
|
||||
} else {
|
||||
_keyPair.physicalKey = null;
|
||||
_keyPair.logicalKey = null;
|
||||
_keyPair.modifiers = [];
|
||||
}
|
||||
if (core.actionHandler.supportedModes.contains(SupportedMode.touch)) {
|
||||
_keyPair.touchPosition = keyPairAction.touchPosition;
|
||||
} else {
|
||||
_keyPair.touchPosition = Offset.zero;
|
||||
}
|
||||
_keyPair.isLongPress = keyPairAction.isLongPress;
|
||||
_keyPair.inGameAction = keyPairAction.inGameAction;
|
||||
_keyPair.inGameActionValue = keyPairAction.inGameActionValue;
|
||||
_keyPair.androidAction = null;
|
||||
setState(() {});
|
||||
},
|
||||
child: Text(keyPairAction.toString()),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -217,42 +242,62 @@ class _ButtonEditPageState extends State<ButtonEditPage> {
|
||||
SelectableCard(
|
||||
icon: RadixIcons.keyboard,
|
||||
title: Text(context.i18n.simulateKeyboardShortcut),
|
||||
isActive: _keyPair.physicalKey != null && !_keyPair.isSpecialKey,
|
||||
isActive:
|
||||
_keyPair.physicalKey != null && !_keyPair.isSpecialKey && core.settings.getLocalEnabled(),
|
||||
value: _keyPair.toString(),
|
||||
onPressed: () async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false, // enable Escape key
|
||||
builder: (c) => HotKeyListenerDialog(
|
||||
customApp: core.actionHandler.supportedApp! as CustomApp,
|
||||
keyPair: _keyPair,
|
||||
),
|
||||
);
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
if (!core.settings.getLocalEnabled()) {
|
||||
buildToast(
|
||||
navigatorKey.currentContext!,
|
||||
title: AppLocalizations.of(context).enableLocalConnectionMethodFirst,
|
||||
);
|
||||
} else {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false, // enable Escape key
|
||||
builder: (c) => HotKeyListenerDialog(
|
||||
customApp: core.actionHandler.supportedApp! as CustomApp,
|
||||
keyPair: _keyPair,
|
||||
),
|
||||
);
|
||||
_keyPair.androidAction = null;
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
}
|
||||
},
|
||||
),
|
||||
if (core.actionHandler.supportedModes.contains(SupportedMode.touch))
|
||||
SelectableCard(
|
||||
title: Text(context.i18n.simulateTouch),
|
||||
icon: core.actionHandler is AndroidActions ? Icons.touch_app_outlined : BootstrapIcons.mouse,
|
||||
isActive: _keyPair.physicalKey == null && _keyPair.touchPosition != Offset.zero,
|
||||
isActive:
|
||||
((core.actionHandler is AndroidActions || _keyPair.physicalKey == null) &&
|
||||
_keyPair.touchPosition != Offset.zero) &&
|
||||
core.settings.getLocalEnabled(),
|
||||
value: _keyPair.toString(),
|
||||
onPressed: () async {
|
||||
if (_keyPair.touchPosition == Offset.zero) {
|
||||
_keyPair.touchPosition = Offset(50, 50);
|
||||
}
|
||||
_keyPair.physicalKey = null;
|
||||
_keyPair.logicalKey = null;
|
||||
await Navigator.of(context).push<bool?>(
|
||||
MaterialPageRoute(
|
||||
builder: (c) => TouchAreaSetupPage(
|
||||
keyPair: _keyPair,
|
||||
if (!core.settings.getLocalEnabled()) {
|
||||
buildToast(
|
||||
navigatorKey.currentContext!,
|
||||
title: AppLocalizations.of(context).enableLocalConnectionMethodFirst,
|
||||
);
|
||||
} else {
|
||||
if (_keyPair.touchPosition == Offset.zero) {
|
||||
_keyPair.touchPosition = Offset(50, 50);
|
||||
}
|
||||
_keyPair.physicalKey = null;
|
||||
_keyPair.logicalKey = null;
|
||||
_keyPair.androidAction = null;
|
||||
await Navigator.of(context).push<bool?>(
|
||||
MaterialPageRoute(
|
||||
builder: (c) => TouchAreaSetupPage(
|
||||
keyPair: _keyPair,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
);
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -260,83 +305,141 @@ class _ButtonEditPageState extends State<ButtonEditPage> {
|
||||
Builder(
|
||||
builder: (context) => SelectableCard(
|
||||
icon: Icons.music_note_outlined,
|
||||
isActive: _keyPair.isSpecialKey,
|
||||
isActive: _keyPair.isSpecialKey && core.settings.getLocalEnabled(),
|
||||
title: Text(context.i18n.simulateMediaKey),
|
||||
value: _keyPair.toString(),
|
||||
onPressed: () {
|
||||
showDropdown(
|
||||
context: context,
|
||||
builder: (c) => DropdownMenu(
|
||||
children: [
|
||||
MenuButton(
|
||||
leading: Icon(Icons.play_arrow_outlined),
|
||||
onPressed: (c) {
|
||||
_keyPair.physicalKey = PhysicalKeyboardKey.mediaPlayPause;
|
||||
_keyPair.logicalKey = null;
|
||||
if (!core.settings.getLocalEnabled()) {
|
||||
buildToast(
|
||||
navigatorKey.currentContext!,
|
||||
title: AppLocalizations.of(context).enableLocalConnectionMethodFirst,
|
||||
);
|
||||
} else {
|
||||
showDropdown(
|
||||
context: context,
|
||||
builder: (c) => DropdownMenu(
|
||||
children: [
|
||||
MenuButton(
|
||||
leading: Icon(Icons.play_arrow_outlined),
|
||||
onPressed: (c) {
|
||||
_keyPair.physicalKey = PhysicalKeyboardKey.mediaPlayPause;
|
||||
_keyPair.touchPosition = Offset.zero;
|
||||
_keyPair.logicalKey = null;
|
||||
_keyPair.androidAction = null;
|
||||
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
},
|
||||
child: Text(context.i18n.playPause),
|
||||
),
|
||||
MenuButton(
|
||||
leading: Icon(Icons.stop_outlined),
|
||||
onPressed: (c) {
|
||||
_keyPair.physicalKey = PhysicalKeyboardKey.mediaStop;
|
||||
_keyPair.logicalKey = null;
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
},
|
||||
child: Text(context.i18n.playPause),
|
||||
),
|
||||
MenuButton(
|
||||
leading: Icon(Icons.stop_outlined),
|
||||
onPressed: (c) {
|
||||
_keyPair.physicalKey = PhysicalKeyboardKey.mediaStop;
|
||||
_keyPair.touchPosition = Offset.zero;
|
||||
_keyPair.logicalKey = null;
|
||||
_keyPair.androidAction = null;
|
||||
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
},
|
||||
child: Text(context.i18n.stop),
|
||||
),
|
||||
MenuButton(
|
||||
leading: Icon(Icons.skip_previous_outlined),
|
||||
onPressed: (c) {
|
||||
_keyPair.physicalKey = PhysicalKeyboardKey.mediaTrackPrevious;
|
||||
_keyPair.logicalKey = null;
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
},
|
||||
child: Text(context.i18n.stop),
|
||||
),
|
||||
MenuButton(
|
||||
leading: Icon(Icons.skip_previous_outlined),
|
||||
onPressed: (c) {
|
||||
_keyPair.physicalKey = PhysicalKeyboardKey.mediaTrackPrevious;
|
||||
_keyPair.touchPosition = Offset.zero;
|
||||
_keyPair.logicalKey = null;
|
||||
_keyPair.androidAction = null;
|
||||
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
},
|
||||
child: Text(context.i18n.previous),
|
||||
),
|
||||
MenuButton(
|
||||
leading: Icon(Icons.skip_next_outlined),
|
||||
onPressed: (c) {
|
||||
_keyPair.physicalKey = PhysicalKeyboardKey.mediaTrackNext;
|
||||
_keyPair.logicalKey = null;
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
},
|
||||
child: Text(context.i18n.previous),
|
||||
),
|
||||
MenuButton(
|
||||
leading: Icon(Icons.skip_next_outlined),
|
||||
onPressed: (c) {
|
||||
_keyPair.physicalKey = PhysicalKeyboardKey.mediaTrackNext;
|
||||
_keyPair.touchPosition = Offset.zero;
|
||||
_keyPair.logicalKey = null;
|
||||
_keyPair.androidAction = null;
|
||||
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
},
|
||||
child: Text(context.i18n.next),
|
||||
),
|
||||
MenuButton(
|
||||
leading: Icon(Icons.volume_up_outlined),
|
||||
onPressed: (c) {
|
||||
_keyPair.physicalKey = PhysicalKeyboardKey.audioVolumeUp;
|
||||
_keyPair.logicalKey = null;
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
},
|
||||
child: Text(context.i18n.next),
|
||||
),
|
||||
MenuButton(
|
||||
leading: Icon(Icons.volume_up_outlined),
|
||||
onPressed: (c) {
|
||||
_keyPair.physicalKey = PhysicalKeyboardKey.audioVolumeUp;
|
||||
_keyPair.touchPosition = Offset.zero;
|
||||
_keyPair.logicalKey = null;
|
||||
_keyPair.androidAction = null;
|
||||
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
},
|
||||
child: Text(context.i18n.volumeUp),
|
||||
),
|
||||
MenuButton(
|
||||
leading: Icon(Icons.volume_down_outlined),
|
||||
child: Text(context.i18n.volumeDown),
|
||||
onPressed: (c) {
|
||||
_keyPair.physicalKey = PhysicalKeyboardKey.audioVolumeDown;
|
||||
_keyPair.logicalKey = null;
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
},
|
||||
child: Text(context.i18n.volumeUp),
|
||||
),
|
||||
MenuButton(
|
||||
leading: Icon(Icons.volume_down_outlined),
|
||||
child: Text(context.i18n.volumeDown),
|
||||
onPressed: (c) {
|
||||
_keyPair.physicalKey = PhysicalKeyboardKey.audioVolumeDown;
|
||||
_keyPair.touchPosition = Offset.zero;
|
||||
_keyPair.logicalKey = null;
|
||||
_keyPair.androidAction = null;
|
||||
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
if (core.logic.showLocalControl && core.actionHandler is AndroidActions)
|
||||
Builder(
|
||||
builder: (context) => SelectableCard(
|
||||
icon: Icons.settings_remote_outlined,
|
||||
isActive: _keyPair.androidAction != null && core.settings.getLocalEnabled(),
|
||||
title: Text(AppLocalizations.of(context).androidSystemAction),
|
||||
value: _keyPair.androidAction?.title,
|
||||
onPressed: () {
|
||||
if (!core.settings.getLocalEnabled()) {
|
||||
buildToast(navigatorKey.currentContext!, title: 'Enable Local Connection method, first.');
|
||||
} else {
|
||||
showDropdown(
|
||||
context: context,
|
||||
builder: (c) => DropdownMenu(
|
||||
children: AndroidSystemAction.values
|
||||
.map(
|
||||
(action) => MenuButton(
|
||||
leading: Icon(action.icon),
|
||||
onPressed: (_) {
|
||||
_keyPair.androidAction = action;
|
||||
_keyPair.physicalKey = null;
|
||||
_keyPair.logicalKey = null;
|
||||
_keyPair.modifiers = [];
|
||||
_keyPair.touchPosition = Offset.zero;
|
||||
_keyPair.inGameAction = null;
|
||||
_keyPair.inGameActionValue = null;
|
||||
setState(() {});
|
||||
widget.onUpdate();
|
||||
},
|
||||
child: Text(action.title),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -369,6 +472,7 @@ class _ButtonEditPageState extends State<ButtonEditPage> {
|
||||
onPressed: (_) {
|
||||
_keyPair.inGameAction = InGameAction.headwindSpeed;
|
||||
_keyPair.inGameActionValue = value;
|
||||
_keyPair.androidAction = null;
|
||||
widget.onUpdate();
|
||||
setState(() {});
|
||||
},
|
||||
@@ -382,6 +486,7 @@ class _ButtonEditPageState extends State<ButtonEditPage> {
|
||||
onPressed: (_) {
|
||||
_keyPair.inGameAction = InGameAction.headwindHeartRateMode;
|
||||
_keyPair.inGameActionValue = null;
|
||||
_keyPair.androidAction = null;
|
||||
widget.onUpdate();
|
||||
setState(() {});
|
||||
},
|
||||
@@ -416,6 +521,7 @@ class _ButtonEditPageState extends State<ButtonEditPage> {
|
||||
_keyPair.touchPosition = Offset.zero;
|
||||
_keyPair.inGameAction = null;
|
||||
_keyPair.inGameActionValue = null;
|
||||
_keyPair.androidAction = null;
|
||||
widget.onUpdate();
|
||||
setState(() {});
|
||||
},
|
||||
@@ -456,6 +562,7 @@ class _ButtonEditPageState extends State<ButtonEditPage> {
|
||||
_keyPair.touchPosition = Offset.zero;
|
||||
_keyPair.physicalKey = null;
|
||||
_keyPair.logicalKey = null;
|
||||
_keyPair.androidAction = null;
|
||||
_keyPair.inGameAction = action;
|
||||
_keyPair.inGameActionValue = ingame;
|
||||
widget.onUpdate();
|
||||
@@ -470,6 +577,7 @@ class _ButtonEditPageState extends State<ButtonEditPage> {
|
||||
_keyPair.touchPosition = Offset.zero;
|
||||
_keyPair.physicalKey = null;
|
||||
_keyPair.logicalKey = null;
|
||||
_keyPair.androidAction = null;
|
||||
_keyPair.inGameAction = action;
|
||||
_keyPair.inGameActionValue = null;
|
||||
widget.onUpdate();
|
||||
|
||||
@@ -53,7 +53,7 @@ class _ConfigurationPageState extends State<ConfigurationPage> {
|
||||
spacing: 4,
|
||||
children: [
|
||||
Text(screenshotMode ? 'Trainer app' : app.name),
|
||||
if (app.supportsOpenBikeProtocol) Icon(Icons.star),
|
||||
if (app.supportsOpenBikeProtocol.isNotEmpty) Icon(Icons.star),
|
||||
],
|
||||
),
|
||||
popup: SelectPopup(
|
||||
@@ -65,7 +65,7 @@ class _ConfigurationPageState extends State<ConfigurationPage> {
|
||||
spacing: 4,
|
||||
children: [
|
||||
Text(app.name),
|
||||
if (app.supportsOpenBikeProtocol) Icon(Icons.star),
|
||||
if (app.supportsOpenBikeProtocol.isNotEmpty) Icon(Icons.star),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -88,7 +88,7 @@ class _ConfigurationPageState extends State<ConfigurationPage> {
|
||||
core.zwiftEmulator.stopAdvertising();
|
||||
}
|
||||
}
|
||||
if (!selectedApp.supportsOpenBikeProtocol) {
|
||||
if (selectedApp.supportsOpenBikeProtocol.isEmpty) {
|
||||
if (core.obpMdnsEmulator.isStarted.value) {
|
||||
core.obpMdnsEmulator.stopServer();
|
||||
}
|
||||
@@ -113,7 +113,7 @@ class _ConfigurationPageState extends State<ConfigurationPage> {
|
||||
},
|
||||
),
|
||||
if (core.settings.getTrainerApp() != null) ...[
|
||||
if (core.settings.getTrainerApp()!.supportsOpenBikeProtocol == true &&
|
||||
if (core.settings.getTrainerApp()!.supportsOpenBikeProtocol.isNotEmpty &&
|
||||
!screenshotMode &&
|
||||
!widget.onboardingMode)
|
||||
Text(
|
||||
@@ -194,13 +194,13 @@ class _ConfigurationPageState extends State<ConfigurationPage> {
|
||||
Future<void> _setTarget(BuildContext context, Target target) async {
|
||||
await core.settings.setLastTarget(target);
|
||||
|
||||
if (core.settings.getTrainerApp()?.supportsOpenBikeProtocol == true && !core.logic.emulatorEnabled) {
|
||||
if ((core.settings.getTrainerApp()?.supportsOpenBikeProtocol.isNotEmpty ?? false) && !core.logic.emulatorEnabled) {
|
||||
core.settings.setObpMdnsEnabled(true);
|
||||
}
|
||||
|
||||
// enable local connection on Windows if the app doesn't support OBP
|
||||
if (target == Target.thisDevice &&
|
||||
core.settings.getTrainerApp()?.supportsOpenBikeProtocol == false &&
|
||||
core.settings.getTrainerApp()?.supportsOpenBikeProtocol.isEmpty == true &&
|
||||
!kIsWeb &&
|
||||
Platform.isWindows) {
|
||||
core.settings.setLocalEnabled(true);
|
||||
|
||||
@@ -65,7 +65,7 @@ class _CustomizeState extends State<CustomizePage> {
|
||||
Expanded(child: Text(a.name)),
|
||||
if (a is CustomApp)
|
||||
BetaPill(text: 'CUSTOM')
|
||||
else if (a.supportsOpenBikeProtocol)
|
||||
else if (a.supportsOpenBikeProtocol.isNotEmpty)
|
||||
Icon(Icons.star, size: 16),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/main.dart';
|
||||
@@ -10,10 +11,13 @@ import 'package:bike_control/widgets/iap_status_widget.dart';
|
||||
import 'package:bike_control/widgets/ignored_devices_dialog.dart';
|
||||
import 'package:bike_control/widgets/scan.dart';
|
||||
import 'package:bike_control/widgets/ui/colored_title.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import '../bluetooth/devices/base_device.dart';
|
||||
import '../utils/keymap/buttons.dart';
|
||||
import 'button_edit.dart';
|
||||
|
||||
class DevicePage extends StatefulWidget {
|
||||
final bool isMobile;
|
||||
@@ -74,12 +78,15 @@ class _DevicePageState extends State<DevicePage> {
|
||||
|
||||
Gap(12),
|
||||
...core.connection.controllerDevices.map(
|
||||
(device) => Card(
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).brightness == Brightness.dark
|
||||
? Theme.of(context).colorScheme.card
|
||||
: Theme.of(context).colorScheme.card.withLuminance(0.95),
|
||||
child: device.showInformation(context),
|
||||
(device) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Card(
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).brightness == Brightness.dark
|
||||
? Theme.of(context).colorScheme.card
|
||||
: Theme.of(context).colorScheme.card.withLuminance(0.95),
|
||||
child: device.showInformation(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -90,16 +97,69 @@ class _DevicePageState extends State<DevicePage> {
|
||||
child: ColoredTitle(text: AppLocalizations.of(context).accessories),
|
||||
),
|
||||
...core.connection.accessories.map(
|
||||
(device) => Card(
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).brightness == Brightness.dark
|
||||
? Theme.of(context).colorScheme.card
|
||||
: Theme.of(context).colorScheme.card.withLuminance(0.95),
|
||||
child: device.showInformation(context),
|
||||
(device) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Card(
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).brightness == Brightness.dark
|
||||
? Theme.of(context).colorScheme.card
|
||||
: Theme.of(context).colorScheme.card.withLuminance(0.95),
|
||||
child: device.showInformation(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
if (!kIsWeb && (Platform.isMacOS || Platform.isWindows))
|
||||
ValueListenableBuilder(
|
||||
valueListenable: core.mediaKeyHandler.isMediaKeyDetectionEnabled,
|
||||
builder: (context, value, child) {
|
||||
return SelectableCard(
|
||||
isActive: value,
|
||||
icon: value ? Icons.check_box : Icons.check_box_outline_blank,
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Text(context.i18n.enableMediaKeyDetection),
|
||||
Text(
|
||||
context.i18n.mediaKeyDetectionTooltip,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
core.mediaKeyHandler.isMediaKeyDetectionEnabled.value =
|
||||
!core.mediaKeyHandler.isMediaKeyDetectionEnabled.value;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(),
|
||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS) && !core.settings.getShowOnboarding())
|
||||
SelectableCard(
|
||||
isActive: core.settings.getPhoneSteeringEnabled(),
|
||||
icon: core.settings.getPhoneSteeringEnabled() ? Icons.check_box : Icons.check_box_outline_blank,
|
||||
title: Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
Icon(InGameAction.navigateRight.icon!, size: 16),
|
||||
Icon(InGameAction.navigateLeft.icon!, size: 16),
|
||||
SizedBox(),
|
||||
Expanded(child: Text(AppLocalizations.of(context).enableSteeringWithPhone)),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
final enable = !core.settings.getPhoneSteeringEnabled();
|
||||
core.settings.setPhoneSteeringEnabled(enable);
|
||||
core.connection.toggleGyroscopeSteering(enable);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
|
||||
Gap(12),
|
||||
if (!screenshotMode)
|
||||
Column(
|
||||
|
||||
@@ -5,7 +5,11 @@ import 'package:bike_control/bluetooth/devices/zwift/zwift_device.dart';
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/keymap/apps/supported_app.dart';
|
||||
import 'package:bike_control/utils/requirements/multi.dart';
|
||||
import 'package:bike_control/utils/requirements/platform.dart';
|
||||
import 'package:bike_control/widgets/apps/openbikecontrol_ble_tile.dart';
|
||||
import 'package:bike_control/widgets/apps/openbikecontrol_mdns_tile.dart';
|
||||
import 'package:bike_control/widgets/scan.dart';
|
||||
import 'package:bike_control/widgets/title.dart';
|
||||
import 'package:bike_control/widgets/ui/help_button.dart';
|
||||
@@ -29,6 +33,7 @@ enum _OnboardingStep {
|
||||
permissions,
|
||||
connect,
|
||||
trainer,
|
||||
openbikecontrol,
|
||||
finish,
|
||||
}
|
||||
|
||||
@@ -103,7 +108,14 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
_OnboardingStep.trainer => _TrainerOnboardingStep(
|
||||
onComplete: () {
|
||||
setState(() {
|
||||
_currentStep = _OnboardingStep.finish;
|
||||
if (core.settings.getTrainerApp()?.supportsOpenBikeProtocol.contains(
|
||||
OpenBikeProtocolSupport.network,
|
||||
) ??
|
||||
false) {
|
||||
_currentStep = _OnboardingStep.openbikecontrol;
|
||||
} else {
|
||||
_currentStep = _OnboardingStep.finish;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
@@ -115,12 +127,13 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
SizedBox(height: 30),
|
||||
Icon(Icons.check_circle, size: 58, color: Colors.green),
|
||||
ColoredTitle(text: AppLocalizations.of(context).setupComplete),
|
||||
Text(
|
||||
AppLocalizations.of(
|
||||
context,
|
||||
).asAFinalStepYoullChooseHowToConnectTo(core.settings.getTrainerApp()?.name ?? 'your trainer'),
|
||||
textAlign: TextAlign.center,
|
||||
).small.muted,
|
||||
if (core.obpMdnsEmulator.connectedApp.value == null)
|
||||
Text(
|
||||
AppLocalizations.of(
|
||||
context,
|
||||
).asAFinalStepYoullChooseHowToConnectTo(core.settings.getTrainerApp()?.name ?? 'your trainer'),
|
||||
textAlign: TextAlign.center,
|
||||
).small.muted,
|
||||
|
||||
SizedBox(height: 30),
|
||||
PrimaryButton(
|
||||
@@ -133,6 +146,13 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
_OnboardingStep.openbikecontrol => _OpenBikeControlConnectPage(
|
||||
onComplete: () {
|
||||
setState(() {
|
||||
_currentStep = _OnboardingStep.finish;
|
||||
});
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -329,3 +349,49 @@ class _TrainerOnboardingStepState extends State<_TrainerOnboardingStep> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _OpenBikeControlConnectPage extends StatefulWidget {
|
||||
final VoidCallback onComplete;
|
||||
const _OpenBikeControlConnectPage({super.key, required this.onComplete});
|
||||
|
||||
@override
|
||||
State<_OpenBikeControlConnectPage> createState() => _OpenBikeControlConnectPageState();
|
||||
}
|
||||
|
||||
class _OpenBikeControlConnectPageState extends State<_OpenBikeControlConnectPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (!core.obpMdnsEmulator.isStarted.value) {
|
||||
if (!core.logic.isObpMdnsEnabled) {
|
||||
core.settings.setObpMdnsEnabled(true);
|
||||
}
|
||||
core.logic.startEnabledConnectionMethod();
|
||||
}
|
||||
core.obpMdnsEmulator.connectedApp.addListener(() {
|
||||
if (core.obpMdnsEmulator.connectedApp.value != null) {
|
||||
widget.onComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
spacing: 22,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context).openBikeControlAnnouncement(core.settings.getTrainerApp()!.name),
|
||||
).small,
|
||||
OpenBikeControlMdnsTile(),
|
||||
|
||||
if (core.settings.getLastTarget() == Target.otherDevice &&
|
||||
core.settings.getTrainerApp()?.supportsOpenBikeProtocol.contains(OpenBikeProtocolSupport.ble) == true) ...[
|
||||
SizedBox(height: 20),
|
||||
Text('If you have issues with your network connection, you can also connect via Bluetooth.').small.muted,
|
||||
OpenBikeControlBluetoothTile(),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ class _TouchAreaSetupPageState extends State<TouchAreaSetupPage> {
|
||||
final flutterView = WidgetsBinding.instance.platformDispatcher.views.first;
|
||||
|
||||
// figure out notch height for e.g. macOS. On Windows the display size is not available (0,0).
|
||||
final differenceInHeight = (flutterView.display.size.height > 0 && !Platform.isIOS)
|
||||
final differenceInHeight = (!Platform.isWindows && flutterView.display.size.height > 0 && !Platform.isIOS)
|
||||
? (flutterView.display.size.height - flutterView.physicalSize.height) / flutterView.devicePixelRatio
|
||||
: 0.0;
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@ import 'package:bike_control/widgets/ui/colored_title.dart';
|
||||
import 'package:bike_control/widgets/ui/toast.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
import '../bluetooth/devices/zwift/protocol/zp.pbenum.dart';
|
||||
import '../utils/keymap/apps/supported_app.dart';
|
||||
|
||||
class TrainerPage extends StatefulWidget {
|
||||
@@ -159,7 +159,7 @@ class _TrainerPageState extends State<TrainerPage> with WidgetsBindingObserver {
|
||||
spacing: 4,
|
||||
children: [
|
||||
Expanded(child: Text(screenshotMode ? 'Trainer app' : app.name)),
|
||||
if (app.supportsOpenBikeProtocol) Icon(Icons.star),
|
||||
if (app.supportsOpenBikeProtocol.isNotEmpty) Icon(Icons.star),
|
||||
],
|
||||
),
|
||||
popup: SelectPopup(
|
||||
@@ -171,7 +171,7 @@ class _TrainerPageState extends State<TrainerPage> with WidgetsBindingObserver {
|
||||
spacing: 4,
|
||||
children: [
|
||||
Text(app.name),
|
||||
if (app.supportsOpenBikeProtocol) Icon(Icons.star),
|
||||
if (app.supportsOpenBikeProtocol.isNotEmpty) Icon(Icons.star),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -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,
|
||||
|
||||
239
lib/pages/unlock.dart
Normal file
239
lib/pages/unlock.dart
Normal file
@@ -0,0 +1,239 @@
|
||||
import 'package:bike_control/bluetooth/devices/zwift/zwift_clickv2.dart';
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/main.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/iap/iap_manager.dart';
|
||||
import 'package:bike_control/widgets/ui/warning.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/src/scheduler/ticker.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
|
||||
import '../widgets/ui/small_progress_indicator.dart';
|
||||
|
||||
class UnlockPage extends StatefulWidget {
|
||||
final ZwiftClickV2 device;
|
||||
const UnlockPage({super.key, required this.device});
|
||||
|
||||
@override
|
||||
State<UnlockPage> createState() => _UnlockPageState();
|
||||
}
|
||||
|
||||
class _UnlockPageState extends State<UnlockPage> with SingleTickerProviderStateMixin {
|
||||
late final bool _wasZwiftMdnsEmulatorActive;
|
||||
late final bool _wasObpMdnsEmulatorActive;
|
||||
bool _showManualSteps = false;
|
||||
|
||||
late final bool _isInTrialPhase;
|
||||
|
||||
late final Ticker _ticker;
|
||||
|
||||
int _secondsRemaining = 60;
|
||||
|
||||
void _isConnectedUpdate() {
|
||||
setState(() {});
|
||||
if (emulator.isUnlocked.value) {
|
||||
_close();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_isInTrialPhase = !IAPManager.instance.isPurchased.value && IAPManager.instance.isTrialExpired;
|
||||
|
||||
_ticker = createTicker((_) {
|
||||
if (emulator.waiting.value) {
|
||||
final waitUntil = emulator.connectionDate!.add(Duration(minutes: 1));
|
||||
final secondsUntil = waitUntil.difference(DateTime.now()).inSeconds;
|
||||
|
||||
if (mounted) {
|
||||
_secondsRemaining = secondsUntil;
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
})..start();
|
||||
|
||||
/*Future.delayed(Duration(seconds: 5), () {
|
||||
emulator.waiting.value = true;
|
||||
|
||||
Future.delayed(Duration(seconds: 3), () {
|
||||
propPrefs.setZwiftClickV2LastUnlock(widget.device.device.deviceId, DateTime.now());
|
||||
emulator.isUnlocked.value = true;
|
||||
});
|
||||
});*/
|
||||
|
||||
_wasZwiftMdnsEmulatorActive = core.zwiftMdnsEmulator.isStarted.value;
|
||||
_wasObpMdnsEmulatorActive = core.obpMdnsEmulator.isStarted.value;
|
||||
if (!_isInTrialPhase) {
|
||||
if (_wasZwiftMdnsEmulatorActive) {
|
||||
core.zwiftMdnsEmulator.stop();
|
||||
core.settings.setZwiftMdnsEmulatorEnabled(false);
|
||||
}
|
||||
if (_wasObpMdnsEmulatorActive) {
|
||||
core.obpMdnsEmulator.stopServer();
|
||||
core.settings.setObpMdnsEnabled(false);
|
||||
}
|
||||
|
||||
emulator.isUnlocked.value = false;
|
||||
emulator.alreadyUnlocked.value = false;
|
||||
emulator.waiting.value = false;
|
||||
emulator.isConnected.addListener(_isConnectedUpdate);
|
||||
emulator.isUnlocked.addListener(_isConnectedUpdate);
|
||||
emulator.alreadyUnlocked.addListener(_isConnectedUpdate);
|
||||
emulator.startServer().then((_) {}).catchError((e, s) {
|
||||
recordError(e, s, context: 'Emulator');
|
||||
core.connection.signalNotification(AlertNotification(LogLevel.LOGLEVEL_ERROR, e.toString()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ticker.dispose();
|
||||
if (!_isInTrialPhase) {
|
||||
emulator.isConnected.removeListener(_isConnectedUpdate);
|
||||
emulator.isUnlocked.removeListener(_isConnectedUpdate);
|
||||
emulator.alreadyUnlocked.removeListener(_isConnectedUpdate);
|
||||
emulator.stop();
|
||||
|
||||
if (_wasZwiftMdnsEmulatorActive) {
|
||||
core.zwiftMdnsEmulator.startServer();
|
||||
core.settings.setZwiftMdnsEmulatorEnabled(true);
|
||||
}
|
||||
if (_wasObpMdnsEmulatorActive) {
|
||||
core.obpMdnsEmulator.startServer();
|
||||
core.settings.setObpMdnsEnabled(true);
|
||||
}
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
constraints: BoxConstraints(maxWidth: 400),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_isInTrialPhase && !_showManualSteps)
|
||||
Text(
|
||||
AppLocalizations.of(context).unlock_yourTrialPhaseHasExpired,
|
||||
)
|
||||
else if (_showManualSteps) ...[
|
||||
Warning(
|
||||
children: [
|
||||
Text(
|
||||
'Important Setup Information',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.destructive,
|
||||
),
|
||||
).small,
|
||||
Text(
|
||||
AppLocalizations.of(context).clickV2Instructions,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.destructive,
|
||||
),
|
||||
).xSmall,
|
||||
if (kDebugMode)
|
||||
GhostButton(
|
||||
onPressed: () {
|
||||
widget.device.sendCommand(Opcode.RESET, null);
|
||||
},
|
||||
child: Text('Reset now'),
|
||||
),
|
||||
|
||||
Button.secondary(
|
||||
onPressed: () {
|
||||
openDrawer(
|
||||
context: context,
|
||||
position: OverlayPosition.bottom,
|
||||
builder: (_) => MarkdownPage(assetPath: 'TROUBLESHOOTING.md'),
|
||||
);
|
||||
},
|
||||
leading: const Icon(Icons.help_outline_outlined),
|
||||
child: Text(context.i18n.instructions),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
Button.primary(
|
||||
child: Text(AppLocalizations.of(context).unlock_markAsUnlocked),
|
||||
onPressed: () {
|
||||
propPrefs.setZwiftClickV2LastUnlock(widget.device.scanResult.deviceId, DateTime.now());
|
||||
closeDrawer(context);
|
||||
},
|
||||
),
|
||||
] else if (!emulator.isConnected.value) ...[
|
||||
Text(AppLocalizations.of(context).unlock_openZwift).li,
|
||||
Text(AppLocalizations.of(context).unlock_connectToBikecontrol).li,
|
||||
SizedBox(height: 32),
|
||||
Text(AppLocalizations.of(context).unlock_bikecontrolAndZwiftNetwork).small,
|
||||
] else if (emulator.alreadyUnlocked.value) ...[
|
||||
Text(AppLocalizations.of(context).unlock_yourZwiftClickMightBeUnlockedAlready),
|
||||
SizedBox(height: 8),
|
||||
Text(AppLocalizations.of(context).unlock_confirmByPressingAButtonOnYourDevice).small,
|
||||
] else if (!emulator.isUnlocked.value)
|
||||
Text(AppLocalizations.of(context).unlock_waitingForZwift)
|
||||
else
|
||||
Text('Zwift Click is unlocked! You can now close this page.'),
|
||||
SizedBox(height: 32),
|
||||
if (!_showManualSteps && !_isInTrialPhase) ...[
|
||||
if (emulator.waiting.value && _secondsRemaining >= 0)
|
||||
Center(child: CircularProgressIndicator(value: 1 - (_secondsRemaining / 60), size: 20))
|
||||
else if (emulator.alreadyUnlocked.value)
|
||||
Center(child: Icon(Icons.lock_clock))
|
||||
else
|
||||
SmallProgressIndicator(),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
if (!emulator.isUnlocked.value && !_showManualSteps) ...[
|
||||
if (!_isInTrialPhase) ...[
|
||||
SizedBox(height: 32),
|
||||
Center(child: Text(AppLocalizations.of(context).unlock_notWorking).small),
|
||||
],
|
||||
SizedBox(height: 6),
|
||||
Center(
|
||||
child: Button.secondary(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_showManualSteps = !_showManualSteps;
|
||||
});
|
||||
},
|
||||
child: Text(AppLocalizations.of(context).unlock_unlockManually),
|
||||
),
|
||||
),
|
||||
],
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _close() {
|
||||
final title = AppLocalizations.of(context).unlock_isnowunlocked(widget.device.toString());
|
||||
|
||||
final subtitle = AppLocalizations.of(context).unlock_youCanNowCloseZwift;
|
||||
core.connection.signalNotification(
|
||||
AlertNotification(LogLevel.LOGLEVEL_INFO, title),
|
||||
);
|
||||
|
||||
core.flutterLocalNotificationsPlugin.show(
|
||||
1339,
|
||||
title,
|
||||
subtitle,
|
||||
NotificationDetails(
|
||||
android: AndroidNotificationDetails('Unlocked', 'Device unlocked notification'),
|
||||
iOS: DarwinNotificationDetails(presentAlert: true),
|
||||
),
|
||||
);
|
||||
closeDrawer(context);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:accessibility/accessibility.dart';
|
||||
import 'package:bike_control/bluetooth/devices/hid/hid_device.dart';
|
||||
import 'package:bike_control/utils/actions/base_actions.dart';
|
||||
@@ -14,6 +16,7 @@ class AndroidActions extends BaseActions {
|
||||
WindowEvent? windowInfo;
|
||||
|
||||
final accessibilityHandler = Accessibility();
|
||||
StreamSubscription<void>? _keymapUpdateSubscription;
|
||||
|
||||
AndroidActions({super.supportedModes = const [SupportedMode.touch, SupportedMode.media]});
|
||||
|
||||
@@ -26,6 +29,15 @@ class AndroidActions extends BaseActions {
|
||||
}
|
||||
});
|
||||
|
||||
// Update handled keys list when keymap changes
|
||||
updateHandledKeys();
|
||||
|
||||
// Listen to keymap changes and update handled keys
|
||||
_keymapUpdateSubscription?.cancel();
|
||||
_keymapUpdateSubscription = supportedApp?.keymap.updateStream.listen((_) {
|
||||
updateHandledKeys();
|
||||
});
|
||||
|
||||
hidKeyPressed().listen((keyPressed) async {
|
||||
final hidDevice = HidDevice(keyPressed.source);
|
||||
final button = hidDevice.getOrAddButton(keyPressed.hidKey, () => ControllerButton(keyPressed.hidKey));
|
||||
@@ -67,6 +79,15 @@ class AndroidActions extends BaseActions {
|
||||
return Success("Key pressed: ${keyPair.toString()}");
|
||||
}
|
||||
|
||||
if (keyPair.androidAction != null) {
|
||||
if (!core.settings.getLocalEnabled() || !core.logic.showLocalControl || !isKeyDown) {
|
||||
return Ignored('Global action ignored');
|
||||
}
|
||||
await accessibilityHandler.performGlobalAction(keyPair.androidAction!.globalAction);
|
||||
await IAPManager.instance.incrementCommandCount();
|
||||
return Success("Global action: ${keyPair.androidAction!.title}");
|
||||
}
|
||||
|
||||
final point = await resolveTouchPosition(keyPair: keyPair, windowInfo: windowInfo);
|
||||
if (point != Offset.zero) {
|
||||
try {
|
||||
@@ -90,4 +111,22 @@ class AndroidActions extends BaseActions {
|
||||
void ignoreHidDevices() {
|
||||
accessibilityHandler.ignoreHidDevices();
|
||||
}
|
||||
|
||||
void updateHandledKeys() {
|
||||
if (supportedApp == null) {
|
||||
accessibilityHandler.setHandledKeys([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all keys from the keymap that have a mapping defined
|
||||
final handledKeys = supportedApp!.keymap.keyPairs
|
||||
.filter((keyPair) => !keyPair.hasNoAction)
|
||||
.expand((keyPair) => keyPair.buttons)
|
||||
.filter((e) => e.action == null && e.icon == null)
|
||||
.map((button) => button.name)
|
||||
.toSet()
|
||||
.toList();
|
||||
|
||||
accessibilityHandler.setHandledKeys(handledKeys);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bike_control/bluetooth/devices/hid/hid_device.dart';
|
||||
import 'package:bike_control/bluetooth/devices/openbikecontrol/obc_ble_emulator.dart';
|
||||
import 'package:bike_control/bluetooth/devices/openbikecontrol/obc_mdns_emulator.dart';
|
||||
import 'package:bike_control/bluetooth/devices/openbikecontrol/protocol_parser.dart';
|
||||
import 'package:bike_control/bluetooth/devices/trainer_connection.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/ftms_mdns_emulator.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pb.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
import 'package:bike_control/bluetooth/devices/zwift/zwift_emulator.dart';
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/bluetooth/remote_pairing.dart';
|
||||
@@ -15,22 +14,21 @@ import 'package:bike_control/utils/actions/android.dart';
|
||||
import 'package:bike_control/utils/actions/base_actions.dart';
|
||||
import 'package:bike_control/utils/actions/remote.dart';
|
||||
import 'package:bike_control/utils/keymap/apps/my_whoosh.dart';
|
||||
import 'package:bike_control/utils/keymap/buttons.dart';
|
||||
import 'package:bike_control/utils/keymap/apps/supported_app.dart';
|
||||
import 'package:bike_control/utils/requirements/android.dart';
|
||||
import 'package:bike_control/utils/settings/settings.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:media_key_detector/media_key_detector.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
import '../bluetooth/connection.dart';
|
||||
import '../bluetooth/devices/mywhoosh/link.dart';
|
||||
import 'keymap/apps/rouvy.dart';
|
||||
import 'media_key_handler.dart';
|
||||
import 'requirements/multi.dart';
|
||||
import 'requirements/platform.dart';
|
||||
import 'smtc_stub.dart' if (dart.library.io) 'package:smtc_windows/smtc_windows.dart';
|
||||
|
||||
final core = Core();
|
||||
|
||||
@@ -67,7 +65,7 @@ class Permissions {
|
||||
} else if (Platform.isMacOS) {
|
||||
list = [
|
||||
BluetoothTurnedOn(),
|
||||
NotificationRequirement(),
|
||||
if (core.settings.getShowOnboarding()) NotificationRequirement(),
|
||||
];
|
||||
} else if (Platform.isIOS) {
|
||||
list = [
|
||||
@@ -170,11 +168,11 @@ class CoreLogic {
|
||||
}
|
||||
|
||||
bool get showObpMdnsEmulator {
|
||||
return core.settings.getTrainerApp()?.supportsOpenBikeProtocol == true;
|
||||
return core.settings.getTrainerApp()?.supportsOpenBikeProtocol.contains(OpenBikeProtocolSupport.network) == true;
|
||||
}
|
||||
|
||||
bool get showObpBluetoothEmulator {
|
||||
return (core.settings.getTrainerApp()?.supportsOpenBikeProtocol == true) &&
|
||||
return (core.settings.getTrainerApp()?.supportsOpenBikeProtocol.contains(OpenBikeProtocolSupport.ble) == true) &&
|
||||
core.settings.getLastTarget() != Target.thisDevice;
|
||||
}
|
||||
|
||||
@@ -209,11 +207,10 @@ class CoreLogic {
|
||||
|
||||
bool get ignoreWarnings =>
|
||||
core.settings.getTrainerApp()?.supportsZwiftEmulation == true ||
|
||||
core.settings.getTrainerApp()?.supportsOpenBikeProtocol == true;
|
||||
core.settings.getTrainerApp()?.supportsOpenBikeProtocol.isNotEmpty == true;
|
||||
|
||||
bool get showLocalRemoteOptions =>
|
||||
core.actionHandler.supportedModes.isNotEmpty &&
|
||||
((showLocalControl && core.settings.getLocalEnabled()) || (isRemoteControlEnabled));
|
||||
core.actionHandler.supportedModes.isNotEmpty && ((showLocalControl) || (isRemoteControlEnabled));
|
||||
|
||||
bool get hasNoConnectionMethod =>
|
||||
!screenshotMode &&
|
||||
@@ -261,9 +258,7 @@ class CoreLogic {
|
||||
Future<bool> isTrainerConnected() async {
|
||||
if (screenshotMode) {
|
||||
return true;
|
||||
} else if (showLocalControl &&
|
||||
core.settings.getLocalEnabled() &&
|
||||
core.settings.getTrainerApp()?.supportsOpenBikeProtocol == false) {
|
||||
} else if (showLocalControl && core.settings.getLocalEnabled()) {
|
||||
if (canRunAndroidService) {
|
||||
return isAndroidServiceRunning();
|
||||
} else {
|
||||
@@ -336,89 +331,3 @@ class CoreLogic {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MediaKeyHandler {
|
||||
final ValueNotifier<bool> isMediaKeyDetectionEnabled = ValueNotifier(false);
|
||||
|
||||
bool _smtcInitialized = false;
|
||||
SMTCWindows? _smtc;
|
||||
|
||||
void initialize() {
|
||||
isMediaKeyDetectionEnabled.addListener(() async {
|
||||
if (!isMediaKeyDetectionEnabled.value) {
|
||||
if (Platform.isWindows) {
|
||||
_smtc?.disableSmtc();
|
||||
} else {
|
||||
mediaKeyDetector.setIsPlaying(isPlaying: false);
|
||||
mediaKeyDetector.removeListener(_onMediaKeyDetectedListener);
|
||||
}
|
||||
} else {
|
||||
if (Platform.isWindows) {
|
||||
if (!_smtcInitialized) {
|
||||
_smtcInitialized = true;
|
||||
await SMTCWindows.initialize();
|
||||
}
|
||||
|
||||
_smtc = SMTCWindows(
|
||||
metadata: const MusicMetadata(
|
||||
title: 'BikeControl Media Key Handler',
|
||||
album: 'BikeControl',
|
||||
albumArtist: 'BikeControl',
|
||||
artist: 'BikeControl',
|
||||
),
|
||||
// Timeline info for the OS media player
|
||||
timeline: const PlaybackTimeline(
|
||||
startTimeMs: 0,
|
||||
endTimeMs: 1000,
|
||||
positionMs: 0,
|
||||
minSeekTimeMs: 0,
|
||||
maxSeekTimeMs: 1000,
|
||||
),
|
||||
config: const SMTCConfig(
|
||||
fastForwardEnabled: true,
|
||||
nextEnabled: true,
|
||||
pauseEnabled: true,
|
||||
playEnabled: true,
|
||||
rewindEnabled: true,
|
||||
prevEnabled: true,
|
||||
stopEnabled: true,
|
||||
),
|
||||
);
|
||||
_smtc!.buttonPressStream.listen(_onMediaKeyPressedListener);
|
||||
} else {
|
||||
mediaKeyDetector.addListener(_onMediaKeyDetectedListener);
|
||||
mediaKeyDetector.setIsPlaying(isPlaying: true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onMediaKeyDetectedListener(MediaKey mediaKey) {
|
||||
_onMediaKeyPressedListener(switch (mediaKey) {
|
||||
MediaKey.playPause => PressedButton.play,
|
||||
MediaKey.rewind => PressedButton.rewind,
|
||||
MediaKey.fastForward => PressedButton.fastForward,
|
||||
MediaKey.volumeUp => PressedButton.channelUp,
|
||||
MediaKey.volumeDown => PressedButton.channelDown,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _onMediaKeyPressedListener(PressedButton mediaKey) async {
|
||||
final hidDevice = HidDevice('HID Device');
|
||||
final keyPressed = mediaKey.name;
|
||||
|
||||
final button = hidDevice.getOrAddButton(
|
||||
keyPressed,
|
||||
() => ControllerButton(keyPressed),
|
||||
);
|
||||
|
||||
var availableDevice = core.connection.controllerDevices.firstOrNullWhere(
|
||||
(e) => e.toString() == hidDevice.toString(),
|
||||
);
|
||||
if (availableDevice == null) {
|
||||
core.connection.addDevices([hidDevice]);
|
||||
availableDevice = hidDevice;
|
||||
}
|
||||
availableDevice.handleButtonsClickedWithoutLongPressSupport([button]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pb.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/main.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pb.dart' as zp;
|
||||
import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/main.dart';
|
||||
@@ -11,9 +10,11 @@ import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:prop/prop.dart' as zp;
|
||||
import 'package:purchases_flutter/purchases_flutter.dart';
|
||||
import 'package:purchases_ui_flutter/purchases_ui_flutter.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:version/version.dart';
|
||||
|
||||
/// RevenueCat-based IAP service for iOS, macOS, and Android
|
||||
class RevenueCatService {
|
||||
@@ -23,6 +24,7 @@ class RevenueCatService {
|
||||
static const String _purchaseStatusKey = 'iap_purchase_status';
|
||||
static const String _dailyCommandCountKey = 'iap_daily_command_count';
|
||||
static const String _lastCommandDateKey = 'iap_last_command_date';
|
||||
static const String _syncedPurchasesKey = 'iap_synced_purchases';
|
||||
|
||||
// RevenueCat entitlement identifier
|
||||
static const String fullVersionEntitlement = 'Full Version';
|
||||
@@ -139,6 +141,11 @@ class RevenueCatService {
|
||||
/// Check if the user has an active entitlement
|
||||
Future<void> _checkExistingPurchase() async {
|
||||
try {
|
||||
final storedStatus = await _prefs.read(key: _syncedPurchasesKey);
|
||||
if (storedStatus != "true") {
|
||||
await _prefs.write(key: _syncedPurchasesKey, value: "true");
|
||||
await Purchases.syncPurchases();
|
||||
}
|
||||
// Check current entitlement status from RevenueCat
|
||||
final customerInfo = await Purchases.getCustomerInfo();
|
||||
await _handleCustomerInfoUpdate(customerInfo);
|
||||
@@ -168,8 +175,14 @@ class RevenueCatService {
|
||||
} else {
|
||||
final purchasedVersion = customerInfo.originalApplicationVersion;
|
||||
core.connection.signalNotification(LogNotification('Apple receipt validated for version: $purchasedVersion'));
|
||||
final purchasedVersionAsInt = int.tryParse(purchasedVersion.toString()) ?? 1337;
|
||||
isPurchasedNotifier.value = purchasedVersionAsInt < (Platform.isMacOS ? 61 : 58);
|
||||
if (purchasedVersion != null && purchasedVersion.contains(".")) {
|
||||
final parsedVersion = Version.parse(purchasedVersion);
|
||||
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) || purchasedVersionAsInt >= 77;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isPurchasedNotifier.value = hasEntitlement;
|
||||
@@ -229,7 +242,7 @@ class RevenueCatService {
|
||||
// Direct the user to the paywall for a better experience
|
||||
if (Platform.isAndroid && !_isAndroidWorking) {
|
||||
buildToast(
|
||||
context,
|
||||
navigatorKey.currentContext!,
|
||||
title: AppLocalizations.of(context).unlockingNotPossible,
|
||||
duration: Duration(seconds: 5),
|
||||
);
|
||||
@@ -301,19 +314,27 @@ class RevenueCatService {
|
||||
|
||||
/// Increment the daily command count
|
||||
Future<void> incrementCommandCount() async {
|
||||
final today = DateTime.now().toIso8601String().split('T')[0];
|
||||
final lastDate = await _prefs.read(key: _lastCommandDateKey);
|
||||
try {
|
||||
final today = DateTime.now().toIso8601String().split('T')[0];
|
||||
final lastDate = await _prefs.read(key: _lastCommandDateKey);
|
||||
|
||||
if (lastDate != today) {
|
||||
// Reset counter for new day
|
||||
_lastCommandDate = today;
|
||||
_dailyCommandCount = 1;
|
||||
await _prefs.write(key: _lastCommandDateKey, value: today);
|
||||
await _prefs.write(key: _dailyCommandCountKey, value: '1');
|
||||
} else {
|
||||
if (lastDate != today) {
|
||||
// Reset counter for new day
|
||||
_lastCommandDate = today;
|
||||
_dailyCommandCount = 1;
|
||||
await _prefs.write(key: _lastCommandDateKey, value: today);
|
||||
await _prefs.write(key: _dailyCommandCountKey, value: '1');
|
||||
} else {
|
||||
final count = _dailyCommandCount ?? 0;
|
||||
_dailyCommandCount = count + 1;
|
||||
await _prefs.write(key: _dailyCommandCountKey, value: _dailyCommandCount.toString());
|
||||
}
|
||||
} catch (e, s) {
|
||||
final count = _dailyCommandCount ?? 0;
|
||||
_dailyCommandCount = count + 1;
|
||||
await _prefs.write(key: _dailyCommandCountKey, value: _dailyCommandCount.toString());
|
||||
// e.g. https://github.com/jonasbark/swiftcontrol/issues/279
|
||||
debugPrint('Error incrementing command count: $e');
|
||||
recordError(e, s, context: 'Incrementing command count');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:bike_control/bluetooth/messages/notification.dart';
|
||||
import 'package:bike_control/main.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/iap/iap_manager.dart';
|
||||
import 'package:bike_control/utils/windows_store_environment.dart';
|
||||
import 'package:bike_control/widgets/ui/toast.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
@@ -70,8 +71,11 @@ class WindowsIAPService {
|
||||
trialDaysRemaining = 0;
|
||||
}
|
||||
} else {
|
||||
final isStorePackaged = await WindowsStoreEnvironment.isPackaged();
|
||||
trial.isActive = isStorePackaged;
|
||||
trialDaysRemaining = 0;
|
||||
}
|
||||
|
||||
if (trial.isActive && !trial.isTrial && trialDaysRemaining <= 0) {
|
||||
IAPManager.instance.isPurchased.value = true;
|
||||
await _prefs.write(key: _purchaseStatusKey, value: "true");
|
||||
@@ -100,7 +104,7 @@ class WindowsIAPService {
|
||||
}
|
||||
|
||||
/// Check if the trial period has started
|
||||
bool get hasTrialStarted => trialDaysRemaining > 0;
|
||||
bool get hasTrialStarted => trialDaysRemaining >= 0;
|
||||
|
||||
/// Get the number of days remaining in the trial
|
||||
int trialDaysRemaining = 0;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:bike_control/utils/keymap/apps/supported_app.dart';
|
||||
import 'package:bike_control/utils/requirements/multi.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../buttons.dart';
|
||||
import '../keymap.dart';
|
||||
@@ -13,7 +13,6 @@ class Biketerra extends SupportedApp {
|
||||
packageName: "biketerra",
|
||||
compatibleTargets: Target.values,
|
||||
supportsZwiftEmulation: true,
|
||||
supportsOpenBikeProtocol: false,
|
||||
keymap: Keymap(
|
||||
keyPairs: [
|
||||
...ControllerButton.values
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:bike_control/main.dart';
|
||||
import 'package:bike_control/utils/keymap/apps/supported_app.dart';
|
||||
import 'package:bike_control/utils/requirements/multi.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
@@ -14,7 +13,6 @@ class MyWhoosh extends SupportedApp {
|
||||
packageName: "com.mywhoosh.whooshgame",
|
||||
compatibleTargets: Target.values,
|
||||
supportsZwiftEmulation: false,
|
||||
supportsOpenBikeProtocol: screenshotMode,
|
||||
star: true,
|
||||
keymap: Keymap(
|
||||
keyPairs: [
|
||||
|
||||
@@ -10,7 +10,7 @@ class OpenBikeControl extends SupportedApp {
|
||||
packageName: "org.openbikecontrol",
|
||||
compatibleTargets: Target.values,
|
||||
supportsZwiftEmulation: false,
|
||||
supportsOpenBikeProtocol: true,
|
||||
supportsOpenBikeProtocol: OpenBikeProtocolSupport.values,
|
||||
keymap: Keymap(
|
||||
keyPairs: [],
|
||||
),
|
||||
|
||||
@@ -9,13 +9,18 @@ import '../keymap.dart';
|
||||
import 'custom_app.dart';
|
||||
import 'my_whoosh.dart';
|
||||
|
||||
enum OpenBikeProtocolSupport {
|
||||
ble,
|
||||
network,
|
||||
}
|
||||
|
||||
abstract class SupportedApp {
|
||||
final List<Target> compatibleTargets;
|
||||
final String packageName;
|
||||
final String name;
|
||||
final Keymap keymap;
|
||||
final bool supportsZwiftEmulation;
|
||||
final bool supportsOpenBikeProtocol;
|
||||
final List<OpenBikeProtocolSupport> supportsOpenBikeProtocol;
|
||||
final bool star;
|
||||
|
||||
const SupportedApp({
|
||||
@@ -24,7 +29,7 @@ abstract class SupportedApp {
|
||||
required this.keymap,
|
||||
required this.compatibleTargets,
|
||||
required this.supportsZwiftEmulation,
|
||||
this.supportsOpenBikeProtocol = false,
|
||||
this.supportsOpenBikeProtocol = const [],
|
||||
this.star = false,
|
||||
});
|
||||
|
||||
|
||||
@@ -14,11 +14,10 @@ import '../keymap.dart';
|
||||
class TrainingPeaks extends SupportedApp {
|
||||
TrainingPeaks()
|
||||
: super(
|
||||
name: 'TrainingPeaks Virtual / IndieVelo',
|
||||
name: 'TrainingPeaks Virtual',
|
||||
packageName: "com.indieVelo.client",
|
||||
compatibleTargets: !kIsWeb && Platform.isIOS ? [Target.otherDevice] : Target.values,
|
||||
supportsZwiftEmulation: false,
|
||||
supportsOpenBikeProtocol: false,
|
||||
star: true,
|
||||
keymap: Keymap(
|
||||
keyPairs: [
|
||||
|
||||
@@ -37,7 +37,9 @@ enum InGameAction {
|
||||
|
||||
// openbikecontrol
|
||||
up('Up', icon: RadixIcons.arrowUp),
|
||||
down('Down', icon: RadixIcons.arrowDown);
|
||||
down('Down', icon: RadixIcons.arrowDown),
|
||||
home('Home', icon: RadixIcons.home),
|
||||
menu('Menu', icon: RadixIcons.dropdownMenu);
|
||||
|
||||
final String title;
|
||||
final bool isLongPress;
|
||||
@@ -54,11 +56,14 @@ enum InGameAction {
|
||||
}
|
||||
|
||||
class ControllerButton {
|
||||
static const int _deviceIdSuffixLength = 4;
|
||||
static const _unset = Object();
|
||||
final String name;
|
||||
final int? identifier;
|
||||
final InGameAction? action;
|
||||
final Color? color;
|
||||
final IconData? icon;
|
||||
final String? sourceDeviceId;
|
||||
|
||||
const ControllerButton(
|
||||
this.name, {
|
||||
@@ -66,8 +71,40 @@ class ControllerButton {
|
||||
this.icon,
|
||||
this.identifier,
|
||||
this.action,
|
||||
this.sourceDeviceId,
|
||||
});
|
||||
|
||||
ControllerButton copyWith({
|
||||
String? name,
|
||||
int? identifier,
|
||||
InGameAction? action,
|
||||
Color? color,
|
||||
IconData? icon,
|
||||
Object? sourceDeviceId = _unset,
|
||||
}) {
|
||||
final newSourceDeviceId = sourceDeviceId == _unset ? this.sourceDeviceId : sourceDeviceId as String?;
|
||||
|
||||
return ControllerButton(
|
||||
name ?? this.name,
|
||||
color: color ?? this.color,
|
||||
icon: icon ?? this.icon,
|
||||
identifier: identifier ?? this.identifier,
|
||||
action: action ?? this.action,
|
||||
sourceDeviceId: newSourceDeviceId,
|
||||
);
|
||||
}
|
||||
|
||||
String get displayName {
|
||||
if (sourceDeviceId == null || true) {
|
||||
return name;
|
||||
}
|
||||
|
||||
final shortenedId = sourceDeviceId!.length <= _deviceIdSuffixLength
|
||||
? sourceDeviceId!
|
||||
: sourceDeviceId!.substring(sourceDeviceId!.length - _deviceIdSuffixLength);
|
||||
return '$name (${shortenedId.toUpperCase()})';
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return name;
|
||||
@@ -82,10 +119,11 @@ class ControllerButton {
|
||||
identifier == other.identifier &&
|
||||
action == other.action &&
|
||||
color == other.color &&
|
||||
icon == other.icon;
|
||||
icon == other.icon &&
|
||||
sourceDeviceId == other.sourceDeviceId;
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(name, action, identifier, color, icon);
|
||||
int get hashCode => Object.hash(name, action, identifier, color, icon, sourceDeviceId);
|
||||
|
||||
static List<ControllerButton> get values => [
|
||||
...SterzoButtons.values,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:accessibility/accessibility.dart';
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/main.dart';
|
||||
import 'package:bike_control/utils/actions/android.dart';
|
||||
@@ -13,6 +14,23 @@ import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import '../actions/base_actions.dart';
|
||||
import 'apps/custom_app.dart';
|
||||
|
||||
enum AndroidSystemAction {
|
||||
back('Back', Icons.arrow_back_ios, GlobalAction.back),
|
||||
dpadCenter('Select', Icons.radio_button_checked_outlined, GlobalAction.dpadCenter),
|
||||
down('Arrow Down', Icons.arrow_downward, GlobalAction.down),
|
||||
right('Arrow Right', Icons.arrow_forward, GlobalAction.right),
|
||||
up('Arrow Up', Icons.arrow_upward, GlobalAction.up),
|
||||
left('Arrow Left', Icons.arrow_back, GlobalAction.left),
|
||||
home('Home', Icons.home_outlined, GlobalAction.home),
|
||||
recents('Recents', Icons.apps, GlobalAction.recents);
|
||||
|
||||
final String title;
|
||||
final IconData icon;
|
||||
final GlobalAction globalAction;
|
||||
|
||||
const AndroidSystemAction(this.title, this.icon, this.globalAction);
|
||||
}
|
||||
|
||||
class Keymap {
|
||||
static Keymap custom = Keymap(keyPairs: []);
|
||||
|
||||
@@ -50,6 +68,7 @@ class Keymap {
|
||||
keyPair.isLongPress = false;
|
||||
keyPair.inGameAction = null;
|
||||
keyPair.inGameActionValue = null;
|
||||
keyPair.androidAction = null;
|
||||
}
|
||||
_updateStream.add(null);
|
||||
}
|
||||
@@ -63,21 +82,20 @@ class Keymap {
|
||||
}
|
||||
}
|
||||
|
||||
ControllerButton getOrAddButton(String name, ControllerButton Function() button) {
|
||||
ControllerButton getOrAddButton(String name, ControllerButton button) {
|
||||
final allButtons = keyPairs.expand((kp) => kp.buttons).toSet().toList();
|
||||
if (allButtons.none((b) => b.name == name)) {
|
||||
final newButton = button();
|
||||
addKeyPair(
|
||||
KeyPair(
|
||||
touchPosition: Offset.zero,
|
||||
buttons: [newButton],
|
||||
buttons: [button],
|
||||
physicalKey: null,
|
||||
logicalKey: null,
|
||||
inGameAction: newButton.action,
|
||||
isLongPress: newButton.action?.isLongPress ?? false,
|
||||
inGameAction: button.action,
|
||||
isLongPress: button.action?.isLongPress ?? false,
|
||||
),
|
||||
);
|
||||
return newButton;
|
||||
return button;
|
||||
} else {
|
||||
return allButtons.firstWhere((b) => b.name == name);
|
||||
}
|
||||
@@ -97,6 +115,10 @@ class Keymap {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void signalUpdate() {
|
||||
_updateStream.add(null);
|
||||
}
|
||||
}
|
||||
|
||||
class KeyPair {
|
||||
@@ -108,6 +130,7 @@ class KeyPair {
|
||||
bool isLongPress;
|
||||
InGameAction? inGameAction;
|
||||
int? inGameActionValue;
|
||||
AndroidSystemAction? androidAction;
|
||||
|
||||
KeyPair({
|
||||
required this.buttons,
|
||||
@@ -118,6 +141,7 @@ class KeyPair {
|
||||
this.isLongPress = false,
|
||||
this.inGameAction,
|
||||
this.inGameActionValue,
|
||||
this.androidAction,
|
||||
});
|
||||
|
||||
bool get isSpecialKey =>
|
||||
@@ -140,8 +164,14 @@ class KeyPair {
|
||||
_ => Icons.keyboard,
|
||||
},
|
||||
//_ when inGameAction != null && core.logic.emulatorEnabled => Icons.link,
|
||||
_ when inGameAction != null && inGameAction!.icon != null => inGameAction!.icon,
|
||||
_ when inGameAction != null && inGameAction!.icon != null && core.logic.emulatorEnabled => inGameAction!.icon,
|
||||
|
||||
_
|
||||
when androidAction != null &&
|
||||
core.logic.showLocalControl &&
|
||||
core.settings.getLocalEnabled() &&
|
||||
core.actionHandler is AndroidActions =>
|
||||
androidAction!.icon,
|
||||
_ when physicalKey != null && core.actionHandler.supportedModes.contains(SupportedMode.keyboard) =>
|
||||
RadixIcons.keyboard,
|
||||
_
|
||||
@@ -155,7 +185,11 @@ class KeyPair {
|
||||
}
|
||||
|
||||
bool get hasNoAction =>
|
||||
logicalKey == null && physicalKey == null && touchPosition == Offset.zero && inGameAction == null;
|
||||
logicalKey == null &&
|
||||
physicalKey == null &&
|
||||
touchPosition == Offset.zero &&
|
||||
inGameAction == null &&
|
||||
androidAction == null;
|
||||
|
||||
bool get hasActiveAction =>
|
||||
screenshotMode ||
|
||||
@@ -163,6 +197,14 @@ class KeyPair {
|
||||
core.logic.showLocalControl &&
|
||||
core.settings.getLocalEnabled() &&
|
||||
core.actionHandler.supportedModes.contains(SupportedMode.keyboard)) ||
|
||||
(isSpecialKey &&
|
||||
core.logic.showLocalControl &&
|
||||
core.settings.getLocalEnabled() &&
|
||||
core.actionHandler is AndroidActions) ||
|
||||
(androidAction != null &&
|
||||
core.logic.showLocalControl &&
|
||||
core.settings.getLocalEnabled() &&
|
||||
core.actionHandler is AndroidActions) ||
|
||||
(touchPosition != Offset.zero &&
|
||||
core.logic.showLocalRemoteOptions &&
|
||||
core.actionHandler.supportedModes.contains(SupportedMode.touch)) ||
|
||||
@@ -189,6 +231,8 @@ class KeyPair {
|
||||
inGameAction!.title,
|
||||
if (inGameActionValue != null) '$inGameActionValue',
|
||||
].joinToString(separator: ': ')
|
||||
: (androidAction != null && core.logic.showLocalControl && core.actionHandler is AndroidActions)
|
||||
? androidAction!.title
|
||||
: (isSpecialKey && core.actionHandler.supportedModes.contains(SupportedMode.media))
|
||||
? switch (physicalKey) {
|
||||
PhysicalKeyboardKey.mediaPlayPause => AppLocalizations.current.playPause,
|
||||
@@ -235,7 +279,16 @@ class KeyPair {
|
||||
// encode to save in preferences
|
||||
|
||||
return jsonEncode({
|
||||
'actions': buttons.map((e) => e.name).toList(),
|
||||
'actions': buttons
|
||||
.map(
|
||||
(e) => e.sourceDeviceId == null
|
||||
? e.name
|
||||
: {
|
||||
'name': e.name,
|
||||
'deviceId': e.sourceDeviceId,
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
if (logicalKey != null) 'logicalKey': logicalKey?.keyId.toString(),
|
||||
if (physicalKey != null) 'physicalKey': physicalKey?.usbHidUsage.toString() ?? '0',
|
||||
if (modifiers.isNotEmpty) 'modifiers': modifiers.map((e) => e.name).toList(),
|
||||
@@ -243,6 +296,7 @@ class KeyPair {
|
||||
'isLongPress': isLongPress,
|
||||
'inGameAction': inGameAction?.name,
|
||||
'inGameActionValue': inGameActionValue,
|
||||
'androidAction': androidAction?.name,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -258,11 +312,33 @@ class KeyPair {
|
||||
)
|
||||
: Offset.zero;
|
||||
|
||||
final buttons = decoded['actions']
|
||||
.map<ControllerButton>(
|
||||
(e) => ControllerButton.values.firstOrNullWhere((element) => element.name == e) ?? ControllerButton(e),
|
||||
)
|
||||
.cast<ControllerButton>()
|
||||
ControllerButton? decodeButton(dynamic raw) {
|
||||
String? name;
|
||||
String? deviceId;
|
||||
|
||||
if (raw is String) {
|
||||
name = raw;
|
||||
} else if (raw is Map) {
|
||||
name = raw['name']?.toString();
|
||||
deviceId = raw['deviceId']?.toString();
|
||||
}
|
||||
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final baseButton = ControllerButton.values.firstOrNullWhere((element) => element.name == name);
|
||||
|
||||
if (baseButton != null) {
|
||||
return baseButton.copyWith(sourceDeviceId: deviceId);
|
||||
}
|
||||
|
||||
return ControllerButton(name, sourceDeviceId: deviceId);
|
||||
}
|
||||
|
||||
final buttons = (decoded['actions'] as List)
|
||||
.map<ControllerButton?>(decodeButton)
|
||||
.whereType<ControllerButton>()
|
||||
.toList();
|
||||
if (buttons.isEmpty) {
|
||||
return null;
|
||||
@@ -291,6 +367,9 @@ class KeyPair {
|
||||
? InGameAction.values.firstOrNullWhere((element) => element.name == decoded['inGameAction'])
|
||||
: null,
|
||||
inGameActionValue: decoded['inGameActionValue'],
|
||||
androidAction: decoded.containsKey('androidAction')
|
||||
? AndroidSystemAction.values.firstOrNullWhere((element) => element.name == decoded['androidAction'])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -305,7 +384,8 @@ class KeyPair {
|
||||
touchPosition == other.touchPosition &&
|
||||
isLongPress == other.isLongPress &&
|
||||
inGameAction == other.inGameAction &&
|
||||
inGameActionValue == other.inGameActionValue;
|
||||
inGameActionValue == other.inGameActionValue &&
|
||||
androidAction == other.androidAction;
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
@@ -316,5 +396,6 @@ class KeyPair {
|
||||
isLongPress,
|
||||
inGameAction,
|
||||
inGameActionValue,
|
||||
androidAction,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -247,9 +247,11 @@ class KeymapManager {
|
||||
} else {
|
||||
final customApp = CustomApp(profileName: newName);
|
||||
|
||||
final connectedDevice = core.connection.devices.firstOrNull;
|
||||
final connectedDeviceButtons = IterableFlatMap(
|
||||
core.connection.controllerDevices,
|
||||
).flatMap((e) => e.availableButtons).toSet();
|
||||
core.actionHandler.supportedApp!.keymap.keyPairs.forEachIndexed((pair, index) {
|
||||
pair.buttons.filter((button) => connectedDevice?.availableButtons.contains(button) == true).forEachIndexed((
|
||||
pair.buttons.filter((button) => connectedDeviceButtons.contains(button) == true).forEachIndexed((
|
||||
button,
|
||||
indexB,
|
||||
) {
|
||||
@@ -288,9 +290,11 @@ class KeymapManager {
|
||||
} else {
|
||||
final customApp = CustomApp(profileName: newName);
|
||||
|
||||
final connectedDevice = core.connection.devices.firstOrNull;
|
||||
final connectedDeviceButtons = IterableFlatMap(
|
||||
core.connection.controllerDevices,
|
||||
).flatMap((e) => e.availableButtons).toSet();
|
||||
core.actionHandler.supportedApp!.keymap.keyPairs.forEachIndexed((pair, index) {
|
||||
pair.buttons.filter((button) => connectedDevice?.availableButtons.contains(button) == true).forEachIndexed((
|
||||
pair.buttons.filter((button) => connectedDeviceButtons.contains(button) == true).forEachIndexed((
|
||||
button,
|
||||
indexB,
|
||||
) {
|
||||
|
||||
127
lib/utils/media_key_handler.dart
Normal file
127
lib/utils/media_key_handler.dart
Normal file
@@ -0,0 +1,127 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bike_control/bluetooth/devices/hid/hid_device.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/keymap/buttons.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
|
||||
import 'package:media_key_detector/media_key_detector.dart';
|
||||
|
||||
import 'smtc_stub.dart' if (dart.library.io) 'package:smtc_windows/smtc_windows.dart';
|
||||
|
||||
class MediaKeyHandler {
|
||||
final ValueNotifier<bool> isMediaKeyDetectionEnabled = ValueNotifier(false);
|
||||
|
||||
bool _smtcInitialized = false;
|
||||
double? _lastVolume;
|
||||
SMTCWindows? _smtc;
|
||||
|
||||
void initialize() {
|
||||
isMediaKeyDetectionEnabled.addListener(() async {
|
||||
if (!isMediaKeyDetectionEnabled.value) {
|
||||
FlutterVolumeController.removeListener();
|
||||
if (Platform.isWindows) {
|
||||
_smtc?.disableSmtc();
|
||||
} else {
|
||||
mediaKeyDetector.setIsPlaying(isPlaying: false);
|
||||
mediaKeyDetector.removeListener(_onMediaKeyDetectedListener);
|
||||
}
|
||||
} else {
|
||||
FlutterVolumeController.addListener(
|
||||
(volume) {
|
||||
_lastVolume ??= volume;
|
||||
if (volume != _lastVolume) {
|
||||
final bool hasAction;
|
||||
if (volume > _lastVolume!) {
|
||||
hasAction = _onMediaKeyDetectedListener(MediaKey.volumeUp);
|
||||
} else {
|
||||
hasAction = _onMediaKeyDetectedListener(MediaKey.volumeDown);
|
||||
}
|
||||
if (hasAction) {
|
||||
// revert volume
|
||||
FlutterVolumeController.setVolume(_lastVolume!);
|
||||
} else {
|
||||
_lastVolume = volume;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
if (Platform.isWindows) {
|
||||
if (!_smtcInitialized) {
|
||||
_smtcInitialized = true;
|
||||
await SMTCWindows.initialize();
|
||||
}
|
||||
|
||||
_smtc = SMTCWindows(
|
||||
metadata: const MusicMetadata(
|
||||
title: 'BikeControl Media Key Handler',
|
||||
album: 'BikeControl',
|
||||
albumArtist: 'BikeControl',
|
||||
artist: 'BikeControl',
|
||||
),
|
||||
// Timeline info for the OS media player
|
||||
timeline: const PlaybackTimeline(
|
||||
startTimeMs: 0,
|
||||
endTimeMs: 1000,
|
||||
positionMs: 0,
|
||||
minSeekTimeMs: 0,
|
||||
maxSeekTimeMs: 1000,
|
||||
),
|
||||
config: const SMTCConfig(
|
||||
fastForwardEnabled: true,
|
||||
nextEnabled: true,
|
||||
pauseEnabled: true,
|
||||
playEnabled: true,
|
||||
rewindEnabled: true,
|
||||
prevEnabled: true,
|
||||
stopEnabled: true,
|
||||
),
|
||||
);
|
||||
_smtc!.buttonPressStream.listen(_onMediaKeyPressedListener);
|
||||
} else {
|
||||
mediaKeyDetector.addListener(_onMediaKeyDetectedListener);
|
||||
mediaKeyDetector.setIsPlaying(isPlaying: true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool _onMediaKeyDetectedListener(MediaKey mediaKey) {
|
||||
final hidDevice = HidDevice('HID Device');
|
||||
|
||||
var availableDevice = core.connection.controllerDevices.firstOrNullWhere(
|
||||
(e) => e.toString() == hidDevice.toString(),
|
||||
);
|
||||
if (availableDevice == null) {
|
||||
core.connection.addDevices([hidDevice]);
|
||||
availableDevice = hidDevice;
|
||||
}
|
||||
|
||||
final keyPressed = mediaKey.name;
|
||||
|
||||
final button = availableDevice.getOrAddButton(
|
||||
keyPressed,
|
||||
() => ControllerButton(keyPressed),
|
||||
);
|
||||
|
||||
availableDevice.handleButtonsClickedWithoutLongPressSupport([button]);
|
||||
|
||||
return core.actionHandler.supportedApp?.keymap.getKeyPair(button)?.hasNoAction == false;
|
||||
}
|
||||
|
||||
bool _onMediaKeyPressedListener(PressedButton mediaKey) {
|
||||
return _onMediaKeyDetectedListener(switch (mediaKey) {
|
||||
PressedButton.play => MediaKey.playPause,
|
||||
PressedButton.pause => MediaKey.playPause,
|
||||
PressedButton.next => MediaKey.fastForward,
|
||||
PressedButton.previous => MediaKey.rewind,
|
||||
PressedButton.stop => MediaKey.playPause,
|
||||
PressedButton.fastForward => MediaKey.fastForward,
|
||||
PressedButton.rewind => MediaKey.rewind,
|
||||
PressedButton.record => throw UnimplementedError(),
|
||||
PressedButton.channelUp => MediaKey.volumeUp,
|
||||
PressedButton.channelDown => MediaKey.volumeDown,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:ui';
|
||||
@@ -114,6 +115,9 @@ class BluetoothConnectRequirement extends PlatformRequirement {
|
||||
}
|
||||
}
|
||||
|
||||
ReceivePort? _receivePort;
|
||||
StreamSubscription? _sub;
|
||||
|
||||
class NotificationRequirement extends PlatformRequirement {
|
||||
NotificationRequirement()
|
||||
: super(
|
||||
@@ -192,22 +196,20 @@ 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',
|
||||
);
|
||||
|
||||
print('NOTIFICATION SETUP');
|
||||
await core.flutterLocalNotificationsPlugin.initialize(
|
||||
InitializationSettings(
|
||||
android: initializationSettingsAndroid,
|
||||
android: AndroidInitializationSettings(
|
||||
'@drawable/ic_notification',
|
||||
),
|
||||
iOS: DarwinInitializationSettings(
|
||||
requestAlertPermission: false,
|
||||
requestBadgePermission: false,
|
||||
requestSoundPermission: false,
|
||||
),
|
||||
macOS: DarwinInitializationSettings(
|
||||
requestAlertPermission: false,
|
||||
@@ -272,10 +274,17 @@ class NotificationRequirement extends PlatformRequirement {
|
||||
),
|
||||
);
|
||||
|
||||
final receivePort = ReceivePort();
|
||||
IsolateNameServer.registerPortWithName(receivePort.sendPort, '_backgroundChannelKey');
|
||||
final backgroundMessagePort = receivePort.asBroadcastStream();
|
||||
backgroundMessagePort.listen((message) {
|
||||
_receivePort = ReceivePort();
|
||||
// If already registered, remove and re-register
|
||||
IsolateNameServer.removePortNameMapping('_backgroundChannelKey');
|
||||
final ok = IsolateNameServer.registerPortWithName(_receivePort!.sendPort, '_backgroundChannelKey');
|
||||
if (!ok) {
|
||||
// If this happens, something else re-registered immediately or you’re in a weird state.
|
||||
throw StateError('Failed to register port name');
|
||||
}
|
||||
final backgroundMessagePort = _receivePort!.asBroadcastStream();
|
||||
_sub = backgroundMessagePort.listen((message) {
|
||||
print('Background isolate received message: $message');
|
||||
if (message == 'disconnect' || message == 'close') {
|
||||
UniversalBle.onAvailabilityChange = null;
|
||||
core.connection.disconnectAll();
|
||||
|
||||
@@ -165,13 +165,13 @@ enum Target {
|
||||
|
||||
return switch (this) {
|
||||
Target.thisDevice => false,
|
||||
_ => supportedApp == null || supportedApp.supportsOpenBikeProtocol == false,
|
||||
_ => supportedApp == null || supportedApp.supportsOpenBikeProtocol.isEmpty,
|
||||
};
|
||||
}
|
||||
|
||||
String getDescription(SupportedApp? app) {
|
||||
final appName = app?.name ?? 'the Trainer app';
|
||||
final preferredConnectionMethod = app?.supportsOpenBikeProtocol == true
|
||||
final preferredConnectionMethod = app?.supportsOpenBikeProtocol.isNotEmpty == true
|
||||
? AppLocalizations.current.openBikeControlConnection
|
||||
: app is MyWhoosh
|
||||
? AppLocalizations.current.myWhooshDirectConnection
|
||||
|
||||
@@ -5,11 +5,13 @@ 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';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider_windows/path_provider_windows.dart';
|
||||
import 'package:prop/emulators/prefs.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:shared_preferences_windows/shared_preferences_windows.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
@@ -25,6 +27,12 @@ class Settings {
|
||||
Future<String?> init({bool retried = false}) async {
|
||||
try {
|
||||
prefs = await SharedPreferences.getInstance();
|
||||
propPrefs.initialize(prefs);
|
||||
try {
|
||||
await NotificationRequirement.setup();
|
||||
} catch (error, stack) {
|
||||
recordError(error, stack, context: 'Notification setup');
|
||||
}
|
||||
initializeActions(getLastTarget()?.connectionType ?? ConnectionType.unknown);
|
||||
|
||||
if (getShowOnboarding() && getTrainerApp() != null) {
|
||||
|
||||
36
lib/utils/windows_store_environment.dart
Normal file
36
lib/utils/windows_store_environment.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// Utilities for determining whether the app is running in a Windows Store/MSIX
|
||||
/// packaged context.
|
||||
///
|
||||
/// Security note:
|
||||
/// - Build-time secrets (e.g. --dart-define) are extractable from the binary.
|
||||
/// - This check is meant to answer a platform truth: "Is this app packaged?"
|
||||
/// - For real entitlement enforcement, rely on Store license APIs (and
|
||||
/// optionally a backend), not on compile-time flags.
|
||||
class WindowsStoreEnvironment {
|
||||
static const MethodChannel _channel = MethodChannel('bike_control/store_env');
|
||||
|
||||
/// Debug-only escape hatch to simulate Store packaged mode.
|
||||
///
|
||||
/// This is intentionally *not* a security feature.
|
||||
static const bool _forcePackagedForDebug = bool.fromEnvironment('FORCE_STORE_PACKAGED', defaultValue: false);
|
||||
|
||||
/// Returns true when running as an MSIX/Store packaged app.
|
||||
///
|
||||
/// In debug/profile you may set `--dart-define=FORCE_STORE_PACKAGED=true`
|
||||
/// for local testing.
|
||||
static Future<bool> isPackaged() async {
|
||||
if (!kReleaseMode && _forcePackagedForDebug) return true;
|
||||
|
||||
try {
|
||||
final result = await _channel.invokeMethod<bool>('isPackaged');
|
||||
return result ?? false;
|
||||
} catch (_) {
|
||||
// If the platform implementation isn't present (e.g., non-Windows),
|
||||
// treat as unpackaged.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,6 +189,7 @@ class _LocalTileState extends State<LocalTile> {
|
||||
),
|
||||
];
|
||||
return ConnectionMethod(
|
||||
supportedActions: null,
|
||||
isEnabled: core.settings.getLocalEnabled(),
|
||||
type: ConnectionMethodType.local,
|
||||
showTroubleshooting: true,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pb.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
import 'package:bike_control/gen/l10n.dart';
|
||||
import 'package:bike_control/main.dart';
|
||||
import 'package:bike_control/pages/markdown.dart';
|
||||
@@ -25,6 +25,7 @@ class _MywhooshLinkTileState extends State<MyWhooshLinkTile> {
|
||||
valueListenable: core.whooshLink.isConnected,
|
||||
builder: (context, isConnected, _) {
|
||||
return ConnectionMethod(
|
||||
supportedActions: core.whooshLink.supportedActions,
|
||||
isEnabled: core.settings.getMyWhooshLinkEnabled(),
|
||||
type: ConnectionMethodType.network,
|
||||
title: context.i18n.connectUsingMyWhooshLink,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:bike_control/bluetooth/devices/zwift/protocol/zp.pb.dart';
|
||||
import 'package:prop/prop.dart';
|
||||
import 'package:bike_control/main.dart';
|
||||
import 'package:bike_control/utils/core.dart';
|
||||
import 'package:bike_control/utils/i18n_extension.dart';
|
||||
@@ -24,6 +24,7 @@ class _OpenBikeProtocolTileState extends State<OpenBikeControlBluetoothTile> {
|
||||
valueListenable: core.obpBluetoothEmulator.connectedApp,
|
||||
builder: (context, isConnected, _) {
|
||||
return ConnectionMethod(
|
||||
supportedActions: isConnected?.supportedActions,
|
||||
isEnabled: core.settings.getObpBleEnabled(),
|
||||
type: ConnectionMethodType.openBikeControl,
|
||||
title: context.i18n.connectUsingBluetooth,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user