mirror of
https://github.com/jonasbark/swiftcontrol.git
synced 2026-02-18 00:17:40 +01:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f83defb37b | ||
|
|
5c8db11536 | ||
|
|
30aa5b33a3 | ||
|
|
ca41e69a17 | ||
|
|
af4d8ab183 | ||
|
|
c1a24cfbd1 | ||
|
|
86b406e2a4 | ||
|
|
1ec93330b0 | ||
|
|
4ed3c5fefe | ||
|
|
54d106ff4e | ||
|
|
996669ec44 | ||
|
|
1d38ff521a | ||
|
|
f0c1409da4 | ||
|
|
9617198db7 | ||
|
|
e4863b1ebd | ||
|
|
d51a4cc29d | ||
|
|
dcbb225355 | ||
|
|
cba449b493 | ||
|
|
559fe1232b | ||
|
|
a7f9ca489e | ||
|
|
74bf75a82e | ||
|
|
747629cebf | ||
|
|
aca6e9272b | ||
|
|
18e6f9a1b5 | ||
|
|
c3532d5c35 | ||
|
|
1a88f45c93 | ||
|
|
b49eda7fc7 | ||
|
|
f0b3bc70b2 | ||
|
|
08700edc22 | ||
|
|
d698c9bbea | ||
|
|
eea1b8eb40 | ||
|
|
0118c5a87c | ||
|
|
65a3374d9c | ||
|
|
536225bf12 | ||
|
|
e858d35617 | ||
|
|
6d87e85353 | ||
|
|
d1fed35c3e | ||
|
|
d9297bd40e | ||
|
|
a1926dfc00 | ||
|
|
d55ba039af | ||
|
|
c9ebc5a9f6 | ||
|
|
be0c2d97ba | ||
|
|
a03cc76eaa | ||
|
|
504c71d5c4 | ||
|
|
d0291c68d7 | ||
|
|
33e5e41eff | ||
|
|
221d5a0b8d | ||
|
|
b899487ee9 | ||
|
|
ff0b724a73 | ||
|
|
647c20a6a3 | ||
|
|
c36e63aa8d | ||
|
|
cb523ea656 | ||
|
|
22b99f4f6d | ||
|
|
05e681b59a |
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
@@ -137,6 +137,7 @@ jobs:
|
||||
with:
|
||||
artifacts: "build/app/outputs/flutter-apk/SwiftControl.android.apk,build/macos/Build/Products/Release/SwiftControl.macos.zip"
|
||||
allowUpdates: true
|
||||
prerelease: ${{ endsWith(env.VERSION, '1337') }}
|
||||
body: "You can also download the Android version from the Play Store: https://play.google.com/store/apps/details?id=de.jonasbark.swiftcontrol"
|
||||
tag: v${{ env.VERSION }}
|
||||
token: ${{ secrets.TOKEN }}
|
||||
@@ -150,6 +151,13 @@ jobs:
|
||||
- name: Web Deploy
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
- name: Extract latest changelog
|
||||
id: changelog
|
||||
run: |
|
||||
chmod +x scripts/get_latest_changelog.sh
|
||||
mkdir -p whatsnew
|
||||
./scripts/get_latest_changelog.sh > whatsnew/whatsnew-en-US
|
||||
|
||||
- name: Upload to Play Store
|
||||
# only upload when env.VERSION does not end with 1337, which is our indicator for beta releases
|
||||
if: "!endsWith(env.VERSION, '1337')"
|
||||
@@ -159,6 +167,7 @@ jobs:
|
||||
packageName: de.jonasbark.swiftcontrol
|
||||
releaseFiles: build/app/outputs/bundle/release/app-release.aab
|
||||
track: production
|
||||
whatsNewDirectory: whatsnew
|
||||
|
||||
windows:
|
||||
needs: build
|
||||
|
||||
49
.github/workflows/web.yml
vendored
Normal file
49
.github/workflows/web.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: "Build"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- web
|
||||
paths:
|
||||
- '.github/workflows/**'
|
||||
- 'lib/**'
|
||||
- 'accessibility/**'
|
||||
- 'keypress_simulator/**'
|
||||
- 'pubspec.yaml'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build & Release
|
||||
runs-on: macos-latest
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
pages: write
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
#1 Checkout Repository
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
#3 Setup Flutter
|
||||
- name: Set Up Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
|
||||
#4 Install Dependencies
|
||||
- name: Install Dependencies
|
||||
run: flutter pub get
|
||||
|
||||
- name: Build Web
|
||||
run: flutter build web --release --base-href "/swiftcontrol/"
|
||||
|
||||
- name: Upload static files as artifact
|
||||
id: deployment
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: build/web
|
||||
|
||||
- name: Web Deploy
|
||||
uses: actions/deploy-pages@v4
|
||||
0
launch.json → .vscode/launch.json
vendored
0
launch.json → .vscode/launch.json
vendored
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,3 +1,15 @@
|
||||
### 2.6.1 (2025-10-01)
|
||||
- fix a few issues with the new touch placement feature
|
||||
- add a workaround for Zwift Click V2 which resets the device when button events are no longer sent
|
||||
|
||||
### 2.6.0 (2025-09-30)
|
||||
- refactor touch placements: show touches on screen, fix misplaced coordinates - should fix #64
|
||||
- show firmware version of connected device
|
||||
- Fix crashes on some Android devices
|
||||
- warn the user how to make Zwift Click V2 work properly
|
||||
- many UI improvements
|
||||
- add setting to enable or disable vibration on button press for Zwift Ride and Zwift Play controllers
|
||||
|
||||
### 2.5.0 (2025-09-25)
|
||||
- Improve usability
|
||||
- SwiftControl is now available via the Play Store: https://play.google.com/store/apps/details?id=de.jonasbark.swiftcontrol
|
||||
|
||||
@@ -20,7 +20,7 @@ https://github.com/user-attachments/assets/1f81b674-1628-4763-ad66-5f3ed7a3f159
|
||||
|
||||
|
||||
## Downloads
|
||||
<a href="https://play.google.com/store/apps/details?id=de.jonasbark.swiftcontrol"><img src="https://storage.googleapis.com/pe-portal-consumer-prod-wagtail-static/images/googleplay-badge-01-getit.max-1920x1070.format-webp.webp?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=wagtail%40pe-portal-consumer-prod.iam.gserviceaccount.com%2F20250925%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20250925T084315Z&X-Goog-Expires=86400&X-Goog-SignedHeaders=host&X-Goog-Signature=6eab941e460ae5973f162ce5740adf1e71cf8dd47fd5a9ba60ec673d31f807b0bea359f123a5f5151eb2315fac9c2aa641886e9fda8c545837274a04ca2e8c3217f54495f3b225ecf55a1ba1a34fe52836562583f387c62a4e140c64d1a13094d455a157df514bf7ea088ec2a2aa294ec5e594aea873ab3b63fc9f6d586ac15c04a0d05a4ec557bcb9cb9de48087508219ebf4bc5686dd8051c9949024baba1933cecdc6035b3766ff9fb9a9dd0c3418b225c155173d3b6911043244966a9df1f06ede2c5128fa7625d168c0c4bebf4e9b4c47439b4056c9fe9056e07399e85f3d875ac3478224e226d778fe8d9e7a8d54cae1a7dceb36494aa0326477ca7ffd" width="220"></a>
|
||||
<a href="https://play.google.com/store/apps/details?id=de.jonasbark.swiftcontrol"><img width="270" height="80" alt="GetItOnGooglePlay_Badge_Web_color_English" src="https://github.com/user-attachments/assets/a059d5a1-2efb-4f65-8117-ef6a99823b21" /></a>
|
||||
|
||||
Get the latest version for free for Windows, macOS and Android here: https://github.com/jonasbark/swiftcontrol/releases
|
||||
|
||||
@@ -34,7 +34,7 @@ Get the latest version for free for Windows, macOS and Android here: https://git
|
||||
|
||||
## Supported Devices
|
||||
- Zwift Click
|
||||
- Zwift Click v2
|
||||
- Zwift Click v2 (mostly, see #68)
|
||||
- Zwift Ride
|
||||
- Zwift Play
|
||||
|
||||
@@ -68,5 +68,5 @@ The app connects to your Zwift device automatically. It does not connect to your
|
||||
Please consider donating to support the development of this app :)
|
||||
|
||||
- [via PayPal](https://paypal.me/boni)
|
||||
- [via CreditCard (USD)](https://donate.stripe.com/8x24gzc5c4ZE3VJdt36J201)
|
||||
- [via CreditCard (EUR)](https://donate.stripe.com/9B6aEX0muajY8bZ1Kl6J200)
|
||||
- [via Credit Card, Google Pay, Apple Pay, etc (USD)](https://donate.stripe.com/8x24gzc5c4ZE3VJdt36J201)
|
||||
- [via Credit Card, Google Pay, Apple Pay, etc (EUR)](https://donate.stripe.com/9B6aEX0muajY8bZ1Kl6J200)
|
||||
|
||||
@@ -41,7 +41,7 @@ class AccessibilityService : AccessibilityService(), Listener {
|
||||
|
||||
private fun getWindowSize(): Rect {
|
||||
val outBounds = Rect()
|
||||
rootInActiveWindow.getBoundsInScreen(outBounds)
|
||||
rootInActiveWindow?.getBoundsInScreen(outBounds)
|
||||
return outBounds
|
||||
}
|
||||
|
||||
|
||||
@@ -26,3 +26,5 @@ linter:
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
formatter:
|
||||
page_width: 120
|
||||
|
||||
@@ -21,6 +21,6 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
<string>13.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '12.0'
|
||||
# platform :ios, '13.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
66
ios/Podfile.lock
Normal file
66
ios/Podfile.lock
Normal file
@@ -0,0 +1,66 @@
|
||||
PODS:
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- Flutter
|
||||
- image_picker_ios (0.0.1):
|
||||
- Flutter
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- universal_ble (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- universal_ble (from `.symlinks/plugins/universal_ble/darwin`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_local_notifications:
|
||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||
image_picker_ios:
|
||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
universal_ble:
|
||||
:path: ".symlinks/plugins/universal_ble/darwin"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f
|
||||
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
|
||||
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
@@ -10,10 +10,12 @@
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
3E50CA021EFA25CF89FE46AB /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2C0E42A04700D6B661C7EE82 /* Pods_RunnerTests.framework */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
9DEFD285994D09CFCE400F36 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE7ADD07A99710C0FB974A8 /* Pods_Runner.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -40,14 +42,20 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0CF32F9ECDBEA4B014717FF8 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
2C0E42A04700D6B661C7EE82 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
5CE7ADD07A99710C0FB974A8 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
7D133E5D5548E2EF2879734F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
86D436F6DAF367742EF27F51 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
8AA6D129479129F106E2298A /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -55,19 +63,44 @@
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
DFFDC4B9C4D6EF6A3BDE2E73 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
EFDECED99A47773C293F8819 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
5046C8DCA17DB268ED17F005 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3E50CA021EFA25CF89FE46AB /* Pods_RunnerTests.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9DEFD285994D09CFCE400F36 /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
31E2F9ED567016937E8AEA3B /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
86D436F6DAF367742EF27F51 /* Pods-Runner.debug.xcconfig */,
|
||||
0CF32F9ECDBEA4B014717FF8 /* Pods-Runner.release.xcconfig */,
|
||||
7D133E5D5548E2EF2879734F /* Pods-Runner.profile.xcconfig */,
|
||||
DFFDC4B9C4D6EF6A3BDE2E73 /* Pods-RunnerTests.debug.xcconfig */,
|
||||
8AA6D129479129F106E2298A /* Pods-RunnerTests.release.xcconfig */,
|
||||
EFDECED99A47773C293F8819 /* Pods-RunnerTests.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -76,6 +109,15 @@
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6A38311855DC1CB8C0E2FD04 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CE7ADD07A99710C0FB974A8 /* Pods_Runner.framework */,
|
||||
2C0E42A04700D6B661C7EE82 /* Pods_RunnerTests.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -94,6 +136,8 @@
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
31E2F9ED567016937E8AEA3B /* Pods */,
|
||||
6A38311855DC1CB8C0E2FD04 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -128,8 +172,10 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
5E1D2B1ED00966C758CA2289 /* [CP] Check Pods Manifest.lock */,
|
||||
331C807D294A63A400263BE5 /* Sources */,
|
||||
331C807F294A63A400263BE5 /* Resources */,
|
||||
5046C8DCA17DB268ED17F005 /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -145,12 +191,15 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
AF2FDC69578083D4D16AB4D6 /* [CP] Check Pods Manifest.lock */,
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
EEF1FBDEE98BA93C4FBDB3AE /* [CP] Embed Pods Frameworks */,
|
||||
1F0C44A79AE73641A1C3FF47 /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -222,6 +271,23 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
1F0C44A79AE73641A1C3FF47 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
@@ -238,6 +304,28 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
5E1D2B1ED00966C758CA2289 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
@@ -253,6 +341,45 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
AF2FDC69578083D4D16AB4D6 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
EEF1FBDEE98BA93C4FBDB3AE /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -346,7 +473,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -379,6 +506,7 @@
|
||||
};
|
||||
331C8088294A63A400263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = DFFDC4B9C4D6EF6A3BDE2E73 /* Pods-RunnerTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@@ -396,6 +524,7 @@
|
||||
};
|
||||
331C8089294A63A400263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 8AA6D129479129F106E2298A /* Pods-RunnerTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@@ -411,6 +540,7 @@
|
||||
};
|
||||
331C808A294A63A400263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = EFDECED99A47773C293F8819 /* Pods-RunnerTests.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
@@ -473,7 +603,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -524,7 +654,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
@@ -54,6 +55,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
3
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
3
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
@@ -4,4 +4,7 @@
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
@@ -45,5 +45,7 @@
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
<string>SwiftControl</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
class BleUuid {
|
||||
static final DEVICE_INFORMATION_SERVICE_UUID = "0000180a-0000-1000-8000-00805f9b34fb".toLowerCase();
|
||||
static final DEVICE_INFORMATION_CHARACTERISTIC_FIRMWARE_REVISION =
|
||||
"00002a26-0000-1000-8000-00805f9b34fb".toLowerCase();
|
||||
|
||||
static final DEVICE_BATTERY_SERVICE_UUID = "0000180f-0000-1000-8000-00805f9b34fb".toLowerCase();
|
||||
static final DEVICE_INFORMATION_CHARACTERISTIC_BATTERY_LEVEL = "00002a19-0000-1000-8000-00805f9b34fb".toLowerCase();
|
||||
static final ZWIFT_CUSTOM_SERVICE_UUID = "00000001-19CA-4651-86E5-FA29DCDD09D1".toLowerCase();
|
||||
static final ZWIFT_RIDE_CUSTOM_SERVICE_UUID = "0000fc82-0000-1000-8000-00805f9b34fb".toLowerCase();
|
||||
static final ZWIFT_ASYNC_CHARACTERISTIC_UUID = "00000002-19CA-4651-86E5-FA29DCDD09D1".toLowerCase();
|
||||
@@ -34,11 +40,45 @@ class Constants {
|
||||
static final REQUEST_START = Uint8List.fromList([0, 9]); //byteArrayOf(1, 2)
|
||||
static final RESPONSE_START_CLICK = Uint8List.fromList([1, 3]); // from device
|
||||
static final RESPONSE_START_PLAY = Uint8List.fromList([1, 4]); // from device
|
||||
static final RESPONSE_START_CLICK_V2 = Uint8List.fromList([0x02, 0x03]); // from device
|
||||
static final RESPONSE_STOPPED_CLICK_V2 = Uint8List.fromList([
|
||||
0xff,
|
||||
0x05,
|
||||
0x00,
|
||||
0xea,
|
||||
0x05,
|
||||
0x19,
|
||||
0x0a,
|
||||
0x0c,
|
||||
0x35,
|
||||
0x38,
|
||||
0x44,
|
||||
0x31,
|
||||
0x35,
|
||||
0x41,
|
||||
0x42,
|
||||
0x42,
|
||||
0x34,
|
||||
0x33,
|
||||
0x36,
|
||||
0x33,
|
||||
0x10,
|
||||
0x01,
|
||||
0x18,
|
||||
0x84,
|
||||
0x07,
|
||||
0x20,
|
||||
0x08,
|
||||
0x28,
|
||||
0x09,
|
||||
0x30,
|
||||
]); // from device
|
||||
|
||||
// Message types received from device
|
||||
static const CONTROLLER_NOTIFICATION_MESSAGE_TYPE = 07;
|
||||
static const EMPTY_MESSAGE_TYPE = 21;
|
||||
static const BATTERY_LEVEL_TYPE = 25;
|
||||
static const UNKNOWN_CLICKV2_TYPE = 0x3C;
|
||||
|
||||
// not figured out the protobuf type this really is, the content is just two varints.
|
||||
static const int CLICK_NOTIFICATION_MESSAGE_TYPE = 55;
|
||||
|
||||
@@ -3,6 +3,9 @@ import 'dart:io';
|
||||
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:swift_control/main.dart';
|
||||
import 'package:swift_control/utils/actions/android.dart';
|
||||
import 'package:swift_control/utils/requirements/android.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
@@ -12,7 +15,7 @@ import 'messages/notification.dart';
|
||||
|
||||
class Connection {
|
||||
final devices = <BaseDevice>[];
|
||||
var androidNotificationsSetup = false;
|
||||
var _androidNotificationsSetup = false;
|
||||
|
||||
final _connectionQueue = <BaseDevice>[];
|
||||
var _handlingConnectionQueue = false;
|
||||
@@ -51,6 +54,7 @@ class Connection {
|
||||
final device = devices.firstOrNullWhere((e) => e.device.deviceId == deviceId);
|
||||
if (device == null) {
|
||||
_actionStreams.add(LogNotification('Device not found: $deviceId'));
|
||||
UniversalBle.disconnect(deviceId);
|
||||
return;
|
||||
} else {
|
||||
device.processCharacteristic(characteristicUuid, value);
|
||||
@@ -94,8 +98,8 @@ class Connection {
|
||||
_handleConnectionQueue();
|
||||
|
||||
hasDevices.value = devices.isNotEmpty;
|
||||
if (devices.isNotEmpty && !androidNotificationsSetup && !kIsWeb && Platform.isAndroid) {
|
||||
androidNotificationsSetup = true;
|
||||
if (devices.isNotEmpty && !_androidNotificationsSetup && !kIsWeb && Platform.isAndroid) {
|
||||
_androidNotificationsSetup = true;
|
||||
NotificationRequirement.setup().catchError((e) {
|
||||
_actionStreams.add(LogNotification(e.toString()));
|
||||
});
|
||||
@@ -164,6 +168,10 @@ class Connection {
|
||||
|
||||
void reset() {
|
||||
_actionStreams.add(LogNotification('Disconnecting all devices'));
|
||||
if (actionHandler is AndroidActions) {
|
||||
AndroidFlutterLocalNotificationsPlugin().stopForegroundService();
|
||||
_androidNotificationsSetup = false;
|
||||
}
|
||||
UniversalBle.stopScan();
|
||||
isScanning.value = false;
|
||||
for (var device in devices) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -28,6 +27,9 @@ abstract class BaseDevice {
|
||||
final zapEncryption = ZapCrypto(LocalKeyProvider());
|
||||
|
||||
bool isConnected = false;
|
||||
bool _isInited = false;
|
||||
int? batteryLevel;
|
||||
String? firmwareVersion;
|
||||
|
||||
bool supportsEncryption = true;
|
||||
|
||||
@@ -40,13 +42,21 @@ abstract class BaseDevice {
|
||||
|
||||
static BaseDevice? fromScanResult(BleDevice scanResult) {
|
||||
// Use the name first as the "System Devices" and Web (android sometimes Windows) don't have manufacturer data
|
||||
final device = switch (scanResult.name) {
|
||||
//'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 Click' => ZwiftClick(scanResult), special case for Zwift Click v2: we must only connect to the left controller
|
||||
_ => null,
|
||||
};
|
||||
final device =
|
||||
kIsWeb
|
||||
? switch (scanResult.name) {
|
||||
'Zwift Ride' => ZwiftRide(scanResult),
|
||||
'Zwift Play' => ZwiftPlay(scanResult),
|
||||
'Zwift Click' => ZwiftClickV2(scanResult),
|
||||
_ => null,
|
||||
}
|
||||
: switch (scanResult.name) {
|
||||
//'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 Click' => ZwiftClick(scanResult), special case for Zwift Click v2: we must only connect to the left controller
|
||||
_ => null,
|
||||
};
|
||||
|
||||
if (device != null) {
|
||||
return device;
|
||||
@@ -89,7 +99,6 @@ abstract class BaseDevice {
|
||||
BleDevice get device => scanResult;
|
||||
final StreamController<BaseNotification> actionStreamInternal = StreamController<BaseNotification>.broadcast();
|
||||
|
||||
int? batteryLevel;
|
||||
Stream<BaseNotification> get actionStream => actionStreamInternal.stream;
|
||||
|
||||
Future<void> connect() async {
|
||||
@@ -99,8 +108,8 @@ abstract class BaseDevice {
|
||||
|
||||
await UniversalBle.connect(device.deviceId);
|
||||
|
||||
if (!kIsWeb && Platform.isAndroid) {
|
||||
//await UniversalBle.requestMtu(device.deviceId, 256);
|
||||
if (!kIsWeb) {
|
||||
await UniversalBle.requestMtu(device.deviceId, 517);
|
||||
}
|
||||
|
||||
final services = await UniversalBle.discoverServices(device.deviceId);
|
||||
@@ -116,6 +125,22 @@ abstract class BaseDevice {
|
||||
);
|
||||
}
|
||||
|
||||
final deviceInformationService = services.firstOrNullWhere(
|
||||
(service) => service.uuid == BleUuid.DEVICE_INFORMATION_SERVICE_UUID,
|
||||
);
|
||||
final firmwareCharacteristic = deviceInformationService?.characteristics.firstOrNullWhere(
|
||||
(c) => c.uuid == BleUuid.DEVICE_INFORMATION_CHARACTERISTIC_FIRMWARE_REVISION,
|
||||
);
|
||||
if (firmwareCharacteristic != null) {
|
||||
final firmwareData = await UniversalBle.read(
|
||||
device.deviceId,
|
||||
deviceInformationService!.uuid,
|
||||
firmwareCharacteristic.uuid,
|
||||
);
|
||||
firmwareVersion = String.fromCharCodes(firmwareData);
|
||||
connection.signalChange(this);
|
||||
}
|
||||
|
||||
final asyncCharacteristic = customService.characteristics.firstOrNullWhere(
|
||||
(characteristic) => characteristic.uuid == BleUuid.ZWIFT_ASYNC_CHARACTERISTIC_UUID,
|
||||
);
|
||||
@@ -133,10 +158,10 @@ abstract class BaseDevice {
|
||||
await UniversalBle.subscribeNotifications(device.deviceId, customService.uuid, asyncCharacteristic.uuid);
|
||||
await UniversalBle.subscribeIndications(device.deviceId, customService.uuid, syncTxCharacteristic.uuid);
|
||||
|
||||
await _setupHandshake();
|
||||
await setupHandshake();
|
||||
}
|
||||
|
||||
Future<void> _setupHandshake() async {
|
||||
Future<void> setupHandshake() async {
|
||||
if (supportsEncryption) {
|
||||
await UniversalBle.write(
|
||||
device.deviceId,
|
||||
@@ -160,12 +185,12 @@ abstract class BaseDevice {
|
||||
}
|
||||
}
|
||||
|
||||
void processCharacteristic(String characteristic, Uint8List bytes) {
|
||||
Future<void> processCharacteristic(String characteristic, Uint8List bytes) async {
|
||||
if (kDebugMode && false) {
|
||||
print('Received $characteristic: ${bytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ')}');
|
||||
print('Received $characteristic: ${String.fromCharCodes(bytes)}');
|
||||
print(
|
||||
"${DateTime.now().toString().split(" ").last} Received data on $characteristic: ${bytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ')}",
|
||||
);
|
||||
}
|
||||
|
||||
if (bytes.isEmpty) {
|
||||
return;
|
||||
}
|
||||
@@ -173,10 +198,8 @@ abstract class BaseDevice {
|
||||
try {
|
||||
if (bytes.startsWith(startCommand)) {
|
||||
_processDevicePublicKeyResponse(bytes);
|
||||
} else if (bytes.startsWith(Constants.RIDE_ON)) {
|
||||
//print("Empty RideOn response - unencrypted mode");
|
||||
} else if (!supportsEncryption || (bytes.length > Int32List.bytesPerElement + EncryptionUtils.MAC_LENGTH)) {
|
||||
_processData(bytes);
|
||||
processData(bytes);
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
print("Error processing data: $e");
|
||||
@@ -191,13 +214,13 @@ abstract class BaseDevice {
|
||||
|
||||
void _processDevicePublicKeyResponse(Uint8List bytes) {
|
||||
final devicePublicKeyBytes = bytes.sublist(Constants.RIDE_ON.length + Constants.RESPONSE_START_CLICK.length);
|
||||
zapEncryption.initialise(devicePublicKeyBytes);
|
||||
if (kDebugMode) {
|
||||
print("Device Public Key - ${devicePublicKeyBytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ')}");
|
||||
}
|
||||
zapEncryption.initialise(devicePublicKeyBytes);
|
||||
}
|
||||
|
||||
void _processData(Uint8List bytes) {
|
||||
Future<void> processData(Uint8List bytes) async {
|
||||
int type;
|
||||
Uint8List message;
|
||||
|
||||
@@ -234,47 +257,10 @@ abstract class BaseDevice {
|
||||
break;
|
||||
case Constants.CLICK_NOTIFICATION_MESSAGE_TYPE:
|
||||
case Constants.PLAY_NOTIFICATION_MESSAGE_TYPE:
|
||||
case Constants.RIDE_NOTIFICATION_MESSAGE_TYPE: // untested
|
||||
case Constants.RIDE_NOTIFICATION_MESSAGE_TYPE:
|
||||
processClickNotification(message)
|
||||
.then((buttonsClicked) async {
|
||||
if (buttonsClicked == null) {
|
||||
// ignore, no changes
|
||||
} else if (buttonsClicked.isEmpty) {
|
||||
actionStreamInternal.add(LogNotification('Buttons released'));
|
||||
_longPressTimer?.cancel();
|
||||
|
||||
// Handle release events for long press keys
|
||||
final buttonsReleased = _previouslyPressedButtons.toList();
|
||||
if (buttonsReleased.isNotEmpty) {
|
||||
await _performRelease(buttonsReleased);
|
||||
}
|
||||
_previouslyPressedButtons.clear();
|
||||
} else {
|
||||
// Handle release events for buttons that are no longer pressed
|
||||
final buttonsReleased = _previouslyPressedButtons.difference(buttonsClicked.toSet()).toList();
|
||||
if (buttonsReleased.isNotEmpty) {
|
||||
await _performRelease(buttonsReleased);
|
||||
}
|
||||
|
||||
final isLongPress =
|
||||
buttonsClicked.singleOrNull != null &&
|
||||
actionHandler.supportedApp?.keymap.getKeyPair(buttonsClicked.single)?.isLongPress == true;
|
||||
|
||||
if (!isLongPress &&
|
||||
!(buttonsClicked.singleOrNull == ZwiftButton.onOffLeft ||
|
||||
buttonsClicked.singleOrNull == ZwiftButton.onOffRight)) {
|
||||
// we don't want to trigger the long press timer for the on/off buttons, also not when it's a long press key
|
||||
_longPressTimer?.cancel();
|
||||
_longPressTimer = Timer.periodic(const Duration(milliseconds: 250), (timer) async {
|
||||
_performActions(buttonsClicked, true);
|
||||
});
|
||||
} else if (isLongPress) {
|
||||
// Update currently pressed buttons
|
||||
_previouslyPressedButtons = buttonsClicked.toSet();
|
||||
}
|
||||
|
||||
_performActions(buttonsClicked, false);
|
||||
}
|
||||
return handleButtonsClicked(buttonsClicked);
|
||||
})
|
||||
.catchError((e) {
|
||||
actionStreamInternal.add(LogNotification(e.toString()));
|
||||
@@ -285,9 +271,51 @@ abstract class BaseDevice {
|
||||
|
||||
Future<List<ZwiftButton>?> processClickNotification(Uint8List message);
|
||||
|
||||
Future<void> handleButtonsClicked(List<ZwiftButton>? buttonsClicked) async {
|
||||
if (buttonsClicked == null) {
|
||||
// ignore, no changes
|
||||
} else if (buttonsClicked.isEmpty) {
|
||||
actionStreamInternal.add(LogNotification('Buttons released'));
|
||||
_longPressTimer?.cancel();
|
||||
|
||||
// Handle release events for long press keys
|
||||
final buttonsReleased = _previouslyPressedButtons.toList();
|
||||
if (buttonsReleased.isNotEmpty) {
|
||||
await _performRelease(buttonsReleased);
|
||||
}
|
||||
_previouslyPressedButtons.clear();
|
||||
} else {
|
||||
// Handle release events for buttons that are no longer pressed
|
||||
final buttonsReleased = _previouslyPressedButtons.difference(buttonsClicked.toSet()).toList();
|
||||
if (buttonsReleased.isNotEmpty) {
|
||||
await _performRelease(buttonsReleased);
|
||||
}
|
||||
|
||||
final isLongPress =
|
||||
buttonsClicked.singleOrNull != null &&
|
||||
actionHandler.supportedApp?.keymap.getKeyPair(buttonsClicked.single)?.isLongPress == true;
|
||||
|
||||
if (!isLongPress &&
|
||||
!(buttonsClicked.singleOrNull == ZwiftButton.onOffLeft ||
|
||||
buttonsClicked.singleOrNull == ZwiftButton.onOffRight)) {
|
||||
// we don't want to trigger the long press timer for the on/off buttons, also not when it's a long press key
|
||||
_longPressTimer?.cancel();
|
||||
_longPressTimer = Timer.periodic(const Duration(milliseconds: 250), (timer) async {
|
||||
_performActions(buttonsClicked, true);
|
||||
});
|
||||
} else if (isLongPress) {
|
||||
// Update currently pressed buttons
|
||||
_previouslyPressedButtons = buttonsClicked.toSet();
|
||||
}
|
||||
|
||||
return _performActions(buttonsClicked, false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _performActions(List<ZwiftButton> buttonsClicked, bool repeated) async {
|
||||
if (!repeated &&
|
||||
buttonsClicked.any(((e) => e.action == InGameAction.shiftDown || e.action == InGameAction.shiftUp))) {
|
||||
buttonsClicked.any(((e) => e.action == InGameAction.shiftDown || e.action == InGameAction.shiftUp)) &&
|
||||
settings.getVibrationEnabled()) {
|
||||
await _vibrate();
|
||||
}
|
||||
for (final action in buttonsClicked) {
|
||||
@@ -319,6 +347,7 @@ abstract class BaseDevice {
|
||||
}
|
||||
|
||||
Future<void> disconnect() async {
|
||||
_isInited = false;
|
||||
_longPressTimer?.cancel();
|
||||
_previouslyPressedButtons.clear();
|
||||
// Release any held keys in long press mode
|
||||
|
||||
@@ -1,8 +1,71 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:swift_control/bluetooth/devices/zwift_ride.dart';
|
||||
|
||||
import '../ble.dart';
|
||||
import '../protocol/zp.pbenum.dart';
|
||||
|
||||
class ZwiftClickV2 extends ZwiftRide {
|
||||
ZwiftClickV2(super.scanResult);
|
||||
|
||||
@override
|
||||
bool get supportsEncryption => false;
|
||||
|
||||
@override
|
||||
List<int> get startCommand => Constants.RIDE_ON + Constants.RESPONSE_START_CLICK_V2;
|
||||
|
||||
@override
|
||||
Future<void> setupHandshake() async {
|
||||
super.setupHandshake();
|
||||
await sendCommandBuffer(Uint8List.fromList([0xFF, 0x04, 0x00]));
|
||||
}
|
||||
|
||||
Future<void> test() async {
|
||||
await sendCommand(Opcode.RESET, null);
|
||||
//await sendCommand(Opcode.GET, Get(dataObjectId: VendorDO.PAGE_DEVICE_PAIRING.value)); // 0008 82E0 03
|
||||
|
||||
/*await sendCommand(Opcode.GET, Get(dataObjectId: DO.PAGE_DEV_INFO.value)); // 0008 00
|
||||
await sendCommand(Opcode.LOG_LEVEL_SET, LogLevelSet(logLevel: LogLevel.LOGLEVEL_TRACE)); // 4108 05
|
||||
|
||||
await sendCommand(Opcode.GET, Get(dataObjectId: DO.PAGE_CLIENT_SERVER_CONFIGURATION.value)); // 0008 10
|
||||
await sendCommand(Opcode.GET, Get(dataObjectId: DO.PAGE_CLIENT_SERVER_CONFIGURATION.value)); // 0008 10
|
||||
await sendCommand(Opcode.GET, Get(dataObjectId: DO.PAGE_CLIENT_SERVER_CONFIGURATION.value)); // 0008 10
|
||||
|
||||
await sendCommand(Opcode.GET, Get(dataObjectId: DO.PAGE_CONTROLLER_INPUT_CONFIG.value)); // 0008 80 08
|
||||
|
||||
await sendCommand(Opcode.GET, Get(dataObjectId: DO.BATTERY_STATE.value)); // 0008 83 06
|
||||
|
||||
// Value: FF04 000A 1540 E9D9 C96B 7463 C27F 1B4E 4D9F 1CB1 205D 882E D7CE
|
||||
// Value: FF04 000A 15B2 6324 0A31 D6C6 B81F C129 D6A4 E99D FFFC B9FC 418D
|
||||
await sendCommandBuffer(
|
||||
Uint8List.fromList([
|
||||
0xFF,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0A,
|
||||
0x15,
|
||||
0xC2,
|
||||
0x63,
|
||||
0x24,
|
||||
0x0A,
|
||||
0x31,
|
||||
0xD6,
|
||||
0xC6,
|
||||
0xB8,
|
||||
0x1F,
|
||||
0xC1,
|
||||
0x29,
|
||||
0xD6,
|
||||
0xA4,
|
||||
0xE9,
|
||||
0x9D,
|
||||
0xFF,
|
||||
0xFC,
|
||||
0xB9,
|
||||
0xFC,
|
||||
0x41,
|
||||
0x8D,
|
||||
]),
|
||||
);*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
import 'package:swift_control/bluetooth/devices/base_device.dart';
|
||||
import 'package:swift_control/bluetooth/devices/zwift_clickv2.dart';
|
||||
import 'package:swift_control/bluetooth/messages/ride_notification.dart';
|
||||
import 'package:swift_control/bluetooth/protocol/zp_vendor.pb.dart';
|
||||
import 'package:swift_control/utils/keymap/buttons.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
import '../../main.dart';
|
||||
import '../ble.dart';
|
||||
import '../messages/notification.dart';
|
||||
import '../protocol/zp.pb.dart';
|
||||
|
||||
class ZwiftRide extends BaseDevice {
|
||||
ZwiftRide(super.scanResult)
|
||||
@@ -39,6 +46,158 @@ class ZwiftRide extends BaseDevice {
|
||||
|
||||
RideNotification? _lastControllerNotification;
|
||||
|
||||
@override
|
||||
Future<void> processData(Uint8List bytes) async {
|
||||
Opcode? opcode;
|
||||
Uint8List message;
|
||||
|
||||
if (supportsEncryption) {
|
||||
final counter = bytes.sublist(0, 4); // Int.SIZE_BYTES is 4
|
||||
final payload = bytes.sublist(4);
|
||||
|
||||
if (zapEncryption.encryptionKeyBytes == null) {
|
||||
actionStreamInternal.add(
|
||||
LogNotification(
|
||||
'Encryption not initialized, yet. You may need to update the firmware of your device with the Zwift Companion app.',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final data = zapEncryption.decrypt(counter, payload);
|
||||
opcode = Opcode.valueOf(data[0]);
|
||||
message = data.sublist(1);
|
||||
} else {
|
||||
opcode = Opcode.valueOf(bytes[0]);
|
||||
message = bytes.sublist(1);
|
||||
}
|
||||
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
'${DateTime.now().toString().split(" ").last} Received $opcode: ${bytes.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ')} => ${String.fromCharCodes(bytes)} ',
|
||||
);
|
||||
}
|
||||
|
||||
if (bytes.startsWith(Constants.RESPONSE_STOPPED_CLICK_V2) && this is ZwiftClickV2) {
|
||||
actionStreamInternal.add(
|
||||
LogNotification(
|
||||
'Your Zwift Click V2 no longer sends events. Connect it in the Zwift app once per day. Resetting the device now.',
|
||||
),
|
||||
);
|
||||
if (!kDebugMode) {
|
||||
sendCommand(Opcode.RESET, null);
|
||||
}
|
||||
}
|
||||
|
||||
switch (opcode) {
|
||||
case Opcode.RIDE_ON:
|
||||
//print("Empty RideOn response - unencrypted mode");
|
||||
|
||||
break;
|
||||
case Opcode.STATUS_RESPONSE:
|
||||
final status = StatusResponse.fromBuffer(message);
|
||||
if (kDebugMode) {
|
||||
print('StatusResponse: ${status.command} status: ${Status.valueOf(status.status)}');
|
||||
}
|
||||
break;
|
||||
case Opcode.GET_RESPONSE:
|
||||
final response = GetResponse.fromBuffer(message);
|
||||
final dataObjectType = DO.valueOf(response.dataObjectId);
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
'GetResponse: ${dataObjectType?.value.toRadixString(16).padLeft(4, '0') ?? response.dataObjectId} $dataObjectType',
|
||||
);
|
||||
}
|
||||
|
||||
switch (dataObjectType) {
|
||||
case DO.PAGE_DEV_INFO:
|
||||
final pageDevInfo = DevInfoPage.fromBuffer(response.dataObjectData);
|
||||
if (kDebugMode) {
|
||||
print('PageDevInfo: $pageDevInfo');
|
||||
}
|
||||
break;
|
||||
case DO.PAGE_DATE_TIME:
|
||||
final pageDateTime = DateTimePage.fromBuffer(response.dataObjectData);
|
||||
if (kDebugMode) {
|
||||
print('PageDateTime: $pageDateTime');
|
||||
}
|
||||
break;
|
||||
case DO.PAGE_CONTROLLER_INPUT_CONFIG:
|
||||
final pageDateTime = ControllerInputConfigPage.fromBuffer(response.dataObjectData);
|
||||
if (kDebugMode) {
|
||||
print('PageDateTime: $pageDateTime');
|
||||
}
|
||||
break;
|
||||
case null:
|
||||
final vendorDO = VendorDO.valueOf(response.dataObjectId);
|
||||
if (kDebugMode) {
|
||||
print('VendorDO: $vendorDO');
|
||||
}
|
||||
switch (vendorDO) {
|
||||
case VendorDO.DEVICE_COUNT:
|
||||
// TODO: Handle this case.
|
||||
break;
|
||||
case VendorDO.NO_CLUE:
|
||||
// TODO: Handle this case.
|
||||
break;
|
||||
case VendorDO.PAGE_DEVICE_PAIRING:
|
||||
final page = DevicePairingDataPage.fromBuffer(response.dataObjectData);
|
||||
if (kDebugMode) {
|
||||
// this should show the right click device
|
||||
// pairingStatus = 1 => connected and paired, otherwise it can be paired but not connected
|
||||
print(
|
||||
'PageDevicePairing: $page => ${page.pairingDevList.map((e) => e.device.reversed.map((d) => d.toRadixString(16).padLeft(2, '0'))).join(', ')}',
|
||||
);
|
||||
}
|
||||
break;
|
||||
case VendorDO.PAIRED_DEVICE:
|
||||
// TODO: Handle this case.
|
||||
break;
|
||||
case VendorDO.PAIRING_STATUS:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Opcode.VENDOR_MESSAGE:
|
||||
final vendorOpCode = VendorOpcode.valueOf(message.second);
|
||||
print('VendorOpcode: $vendorOpCode');
|
||||
break;
|
||||
case Opcode.LOG_DATA:
|
||||
final logMessage = LogDataNotification.fromBuffer(message);
|
||||
if (kDebugMode) {
|
||||
actionStreamInternal.add(LogNotification(logMessage.toString()));
|
||||
}
|
||||
break;
|
||||
case Opcode.BATTERY_NOTIF:
|
||||
final notification = BatteryNotification.fromBuffer(message);
|
||||
if (batteryLevel != notification.newPercLevel) {
|
||||
batteryLevel = notification.newPercLevel;
|
||||
connection.signalChange(this);
|
||||
}
|
||||
break;
|
||||
case Opcode.CONTROLLER_NOTIFICATION:
|
||||
processClickNotification(message)
|
||||
.then((buttonsClicked) async {
|
||||
return handleButtonsClicked(buttonsClicked);
|
||||
})
|
||||
.catchError((e) {
|
||||
actionStreamInternal.add(LogNotification(e.toString()));
|
||||
});
|
||||
break;
|
||||
case null:
|
||||
if (bytes[0] == 0x1A) {
|
||||
final batteryStatus = BatteryStatus.fromBuffer(message);
|
||||
if (kDebugMode) {
|
||||
print('BatteryStatus: $batteryStatus');
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ZwiftButton>?> processClickNotification(Uint8List message) async {
|
||||
final RideNotification clickNotification = RideNotification(message);
|
||||
@@ -53,4 +212,32 @@ class ZwiftRide extends BaseDevice {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendCommand(Opcode opCode, $pb.GeneratedMessage? message) async {
|
||||
final buffer = Uint8List.fromList([opCode.value, ...message?.writeToBuffer() ?? []]);
|
||||
if (kDebugMode) {
|
||||
print("Sending $opCode: ${buffer.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ')}");
|
||||
}
|
||||
await UniversalBle.write(
|
||||
device.deviceId,
|
||||
customServiceId,
|
||||
syncRxCharacteristic!.uuid,
|
||||
buffer,
|
||||
withoutResponse: true,
|
||||
);
|
||||
await Future.delayed(Duration(milliseconds: 500));
|
||||
}
|
||||
|
||||
Future<void> sendCommandBuffer(Uint8List buffer) async {
|
||||
if (kDebugMode) {
|
||||
print("Sending ${buffer.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ')}");
|
||||
}
|
||||
await UniversalBle.write(
|
||||
device.deviceId,
|
||||
customServiceId,
|
||||
syncRxCharacteristic!.uuid,
|
||||
buffer,
|
||||
withoutResponse: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:typed_data';
|
||||
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:swift_control/utils/keymap/buttons.dart';
|
||||
import 'package:swift_control/widgets/keymap_explanation.dart';
|
||||
|
||||
import '../protocol/zwift.pb.dart';
|
||||
import 'notification.dart';
|
||||
@@ -19,7 +20,7 @@ class ClickNotification extends BaseNotification {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Buttons: ${buttonsClicked.joinToString(transform: (e) => e.name)}';
|
||||
return 'Buttons: ${buttonsClicked.joinToString(transform: (e) => e.name.splitByUpperCase())}';
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:dartx/dartx.dart';
|
||||
import 'package:swift_control/bluetooth/messages/notification.dart';
|
||||
import 'package:swift_control/bluetooth/protocol/zwift.pb.dart';
|
||||
import 'package:swift_control/utils/keymap/buttons.dart';
|
||||
import 'package:swift_control/widgets/keymap_explanation.dart';
|
||||
|
||||
class PlayNotification extends BaseNotification {
|
||||
late List<ZwiftButton> buttonsClicked;
|
||||
@@ -35,7 +36,7 @@ class PlayNotification extends BaseNotification {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Buttons: ${buttonsClicked.joinToString(transform: (e) => e.name)}';
|
||||
return 'Buttons: ${buttonsClicked.joinToString(transform: (e) => e.name.splitByUpperCase())}';
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:dartx/dartx.dart';
|
||||
import 'package:swift_control/bluetooth/messages/notification.dart';
|
||||
import 'package:swift_control/bluetooth/protocol/zwift.pb.dart';
|
||||
import 'package:swift_control/utils/keymap/buttons.dart';
|
||||
import 'package:swift_control/widgets/keymap_explanation.dart';
|
||||
|
||||
enum _RideButtonMask {
|
||||
LEFT_BTN(0x00001),
|
||||
@@ -72,7 +73,7 @@ class RideNotification extends BaseNotification {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Buttons: ${buttonsClicked.joinToString(transform: (e) => e.name)}';
|
||||
return 'Buttons: ${buttonsClicked.joinToString(transform: (e) => e.name.splitByUpperCase())}';
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
6146
lib/bluetooth/protocol/zp.pb.dart
Normal file
6146
lib/bluetooth/protocol/zp.pb.dart
Normal file
File diff suppressed because it is too large
Load Diff
583
lib/bluetooth/protocol/zp.pbenum.dart
Normal file
583
lib/bluetooth/protocol/zp.pbenum.dart
Normal file
@@ -0,0 +1,583 @@
|
||||
//
|
||||
// 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');
|
||||
1692
lib/bluetooth/protocol/zp.pbjson.dart
Normal file
1692
lib/bluetooth/protocol/zp.pbjson.dart
Normal file
File diff suppressed because it is too large
Load Diff
14
lib/bluetooth/protocol/zp.pbserver.dart
Normal file
14
lib/bluetooth/protocol/zp.pbserver.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// 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';
|
||||
|
||||
896
lib/bluetooth/protocol/zp_vendor.pb.dart
Normal file
896
lib/bluetooth/protocol/zp_vendor.pb.dart
Normal file
@@ -0,0 +1,896 @@
|
||||
//
|
||||
// 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');
|
||||
101
lib/bluetooth/protocol/zp_vendor.pbenum.dart
Normal file
101
lib/bluetooth/protocol/zp_vendor.pbenum.dart
Normal file
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// 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');
|
||||
267
lib/bluetooth/protocol/zp_vendor.pbjson.dart
Normal file
267
lib/bluetooth/protocol/zp_vendor.pbjson.dart
Normal file
@@ -0,0 +1,267 @@
|
||||
//
|
||||
// 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==');
|
||||
|
||||
14
lib/bluetooth/protocol/zp_vendor.pbserver.dart
Normal file
14
lib/bluetooth/protocol/zp_vendor.pbserver.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// 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';
|
||||
|
||||
@@ -24,7 +24,7 @@ void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
if (kIsWeb) {
|
||||
actionHandler = StubActions();
|
||||
} else if (Platform.isAndroid) {
|
||||
} else if (Platform.isAndroid || Platform.isIOS) {
|
||||
actionHandler = AndroidActions();
|
||||
} else {
|
||||
actionHandler = DesktopActions();
|
||||
|
||||
98
lib/pages/changelog.dart
Normal file
98
lib/pages/changelog.dart
Normal file
@@ -0,0 +1,98 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:swift_control/utils/changelog.dart';
|
||||
|
||||
class ChangelogPage extends StatefulWidget {
|
||||
const ChangelogPage({super.key});
|
||||
|
||||
@override
|
||||
State<ChangelogPage> createState() => _ChangelogPageState();
|
||||
}
|
||||
|
||||
class _ChangelogPageState extends State<ChangelogPage> {
|
||||
List<ChangelogEntry>? _entries;
|
||||
String? _error;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadChangelog();
|
||||
}
|
||||
|
||||
Future<void> _loadChangelog() async {
|
||||
try {
|
||||
final entries = await ChangelogParser.parse();
|
||||
setState(() {
|
||||
_entries = entries;
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_error = 'Failed to load changelog: $e';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Changelog'),
|
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
),
|
||||
body: _error != null
|
||||
? Center(child: Text(_error!))
|
||||
: _entries == null
|
||||
? Center(child: CircularProgressIndicator())
|
||||
: ListView.builder(
|
||||
padding: EdgeInsets.all(16),
|
||||
itemCount: _entries!.length,
|
||||
itemBuilder: (context, index) {
|
||||
final entry = _entries![index];
|
||||
return Card(
|
||||
margin: EdgeInsets.only(bottom: 16),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Version ${entry.version}',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
entry.date,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
...entry.changes.map(
|
||||
(change) => Padding(
|
||||
padding: EdgeInsets.only(bottom: 4),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('• ', style: TextStyle(fontSize: 16)),
|
||||
Expanded(
|
||||
child: Text(
|
||||
change,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,12 @@ import 'dart:io';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:swift_control/bluetooth/devices/zwift_clickv2.dart';
|
||||
import 'package:swift_control/main.dart';
|
||||
import 'package:swift_control/pages/touch_area.dart';
|
||||
import 'package:swift_control/widgets/keymap_explanation.dart';
|
||||
import 'package:swift_control/widgets/logviewer.dart';
|
||||
import 'package:swift_control/widgets/testbed.dart';
|
||||
import 'package:swift_control/widgets/title.dart';
|
||||
|
||||
import '../bluetooth/devices/base_device.dart';
|
||||
@@ -52,120 +54,172 @@ class _DevicePageState extends State<DevicePage> {
|
||||
onPopInvokedWithResult: (hello, _) {
|
||||
connection.reset();
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: AppTitle(),
|
||||
actions: buildMenuButtons(),
|
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 10,
|
||||
children: [
|
||||
Text('Connected Devices:', style: Theme.of(context).textTheme.titleMedium),
|
||||
Text(
|
||||
connection.devices.joinToString(
|
||||
separator: '\n',
|
||||
transform: (it) {
|
||||
return "${it.device.name ?? it.runtimeType}: ${it.isConnected ? 'Connected' : 'Not connected'}${it.batteryLevel != null ? ' - Battery Level: ${it.batteryLevel}%' : ''}";
|
||||
},
|
||||
),
|
||||
),
|
||||
Divider(color: Theme.of(context).colorScheme.primary, height: 30),
|
||||
if (!kIsWeb)
|
||||
Column(
|
||||
spacing: 12,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flex(
|
||||
child: Stack(
|
||||
children: [
|
||||
Scaffold(
|
||||
appBar: AppBar(
|
||||
title: AppTitle(),
|
||||
actions: buildMenuButtons(),
|
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 10,
|
||||
children: [
|
||||
Text('Connected Devices:', style: Theme.of(context).textTheme.titleMedium),
|
||||
|
||||
if (connection.devices.any((device) => (device is ZwiftClickV2) && device.isConnected))
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.errorContainer,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
'''To make your Zwift Click V2 work best you should connect it in the Zwift app once each day.\nIf you don't do that SwiftControl will need to reconnect every minute.
|
||||
|
||||
1. Open Zwift app
|
||||
2. Log in (subscription not required) and open the device connection screen
|
||||
3. Connect your Trainer, then connect the Zwift Click V2
|
||||
4. Close the Zwift app again and connect again in SwiftControl''',
|
||||
),
|
||||
),
|
||||
Text(
|
||||
connection.devices.joinToString(
|
||||
separator: '\n',
|
||||
transform: (it) {
|
||||
return """${it.device.name ?? it.runtimeType}: ${it.isConnected ? 'Connected' : 'Not connected'}
|
||||
${it.batteryLevel != null ? ' - Battery Level: ${it.batteryLevel}%' : ''}
|
||||
${it.firmwareVersion != null ? ' - Firmware Version: ${it.firmwareVersion}' : ''}""".trim();
|
||||
},
|
||||
),
|
||||
),
|
||||
Divider(color: Theme.of(context).colorScheme.primary, height: 30),
|
||||
if (!kIsWeb)
|
||||
Column(
|
||||
spacing: 12,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
direction: MediaQuery.sizeOf(context).width > 600 ? Axis.horizontal : Axis.vertical,
|
||||
spacing: 8,
|
||||
children: [
|
||||
DropdownMenu<SupportedApp>(
|
||||
controller: controller,
|
||||
dropdownMenuEntries:
|
||||
SupportedApp.supportedApps
|
||||
.map((app) => DropdownMenuEntry<SupportedApp>(value: app, label: app.name))
|
||||
.toList(),
|
||||
label: Text('Select Keymap / app'),
|
||||
onSelected: (app) async {
|
||||
if (app == null) {
|
||||
return;
|
||||
}
|
||||
controller.text = app.name ?? '';
|
||||
actionHandler.supportedApp = app;
|
||||
settings.setApp(app);
|
||||
setState(() {});
|
||||
if (app is! CustomApp && !kIsWeb && (Platform.isMacOS || Platform.isWindows)) {
|
||||
_snackBarMessengerKey.currentState!.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Customize the keymap if you experience any issues (e.g. wrong keyboard output)',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
initialSelection: actionHandler.supportedApp,
|
||||
hintText: 'Select your Keymap',
|
||||
Flex(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
direction: MediaQuery.sizeOf(context).width > 600 ? Axis.horizontal : Axis.vertical,
|
||||
spacing: 8,
|
||||
children: [
|
||||
DropdownMenu<SupportedApp>(
|
||||
controller: controller,
|
||||
dropdownMenuEntries:
|
||||
SupportedApp.supportedApps
|
||||
.map((app) => DropdownMenuEntry<SupportedApp>(value: app, label: app.name))
|
||||
.toList(),
|
||||
label: Text('Select Keymap / app'),
|
||||
onSelected: (app) async {
|
||||
if (app == null) {
|
||||
return;
|
||||
}
|
||||
controller.text = app.name ?? '';
|
||||
actionHandler.supportedApp = app;
|
||||
await settings.setApp(app);
|
||||
setState(() {});
|
||||
if (app is! CustomApp && !kIsWeb && (Platform.isMacOS || Platform.isWindows)) {
|
||||
_snackBarMessengerKey.currentState!.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Customize the keymap if you experience any issues (e.g. wrong keyboard output)',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
initialSelection: actionHandler.supportedApp,
|
||||
hintText: 'Select your Keymap',
|
||||
),
|
||||
|
||||
if (actionHandler.supportedApp != null)
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
if (actionHandler.supportedApp! is! CustomApp) {
|
||||
final customApp = CustomApp();
|
||||
|
||||
final connectedDevice = connection.devices.firstOrNull;
|
||||
actionHandler.supportedApp!.keymap.keyPairs.forEachIndexed((pair, index) {
|
||||
pair.buttons
|
||||
.filter(
|
||||
(button) => connectedDevice?.availableButtons.contains(button) == true,
|
||||
)
|
||||
.forEachIndexed((button, indexB) {
|
||||
customApp.setKey(
|
||||
button,
|
||||
physicalKey: pair.physicalKey!,
|
||||
logicalKey: pair.logicalKey,
|
||||
isLongPress: pair.isLongPress,
|
||||
touchPosition:
|
||||
pair.touchPosition != Offset.zero
|
||||
? pair.touchPosition
|
||||
: Offset(((indexB + 1)) * 100, 200 + (index * 100)),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
actionHandler.supportedApp = customApp;
|
||||
await settings.setApp(customApp);
|
||||
}
|
||||
final result = await Navigator.of(
|
||||
context,
|
||||
).push<bool>(MaterialPageRoute(builder: (_) => TouchAreaSetupPage()));
|
||||
|
||||
if (result == true && actionHandler.supportedApp is CustomApp) {
|
||||
await settings.setApp(actionHandler.supportedApp!);
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
child: Text('Customize Keymap'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
if (actionHandler.supportedApp != null)
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
if (actionHandler.supportedApp! is! CustomApp) {
|
||||
final customApp = CustomApp();
|
||||
|
||||
actionHandler.supportedApp!.keymap.keyPairs.forEachIndexed((pair, index) {
|
||||
pair.buttons.forEachIndexed((button, indexB) {
|
||||
customApp.setKey(
|
||||
button,
|
||||
physicalKey: pair.physicalKey!,
|
||||
logicalKey: pair.logicalKey,
|
||||
isLongPress: pair.isLongPress,
|
||||
touchPosition:
|
||||
pair.touchPosition != Offset.zero
|
||||
? pair.touchPosition
|
||||
: Offset(((indexB + 1)) * 100, 200 + (index * 100)),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
actionHandler.supportedApp = customApp;
|
||||
settings.setApp(customApp);
|
||||
}
|
||||
final result = await Navigator.of(
|
||||
context,
|
||||
).push<bool>(MaterialPageRoute(builder: (_) => TouchAreaSetupPage()));
|
||||
if (result == true && actionHandler.supportedApp is CustomApp) {
|
||||
settings.setApp(actionHandler.supportedApp!);
|
||||
}
|
||||
KeymapExplanation(
|
||||
key: Key(actionHandler.supportedApp!.keymap.runtimeType.toString()),
|
||||
keymap: actionHandler.supportedApp!.keymap,
|
||||
onUpdate: () {
|
||||
setState(() {});
|
||||
controller.text = actionHandler.supportedApp?.name ?? '';
|
||||
},
|
||||
),
|
||||
if (connection.devices.any(
|
||||
(device) =>
|
||||
(device.device.name == 'Zwift Ride' || device.device.name == 'Zwift Play') &&
|
||||
device.isConnected,
|
||||
))
|
||||
SwitchListTile(
|
||||
title: Text('Vibration on Shift'),
|
||||
subtitle: Text('Enable vibration feedback when shifting gears'),
|
||||
value: settings.getVibrationEnabled(),
|
||||
onChanged: (value) async {
|
||||
await settings.setVibrationEnabled(value);
|
||||
setState(() {});
|
||||
},
|
||||
child: Text('Customize Keymap'),
|
||||
),
|
||||
if (kDebugMode &&
|
||||
connection.devices.any((device) => (device is ZwiftClickV2) && device.isConnected))
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
(connection.devices.first as ZwiftClickV2).test();
|
||||
},
|
||||
child: Text('Test'),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (actionHandler.supportedApp != null)
|
||||
KeymapExplanation(
|
||||
key: Key(actionHandler.supportedApp!.keymap.runtimeType.toString()),
|
||||
keymap: actionHandler.supportedApp!.keymap,
|
||||
onUpdate: () {
|
||||
setState(() {});
|
||||
controller.text = actionHandler.supportedApp?.name ?? '';
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 800, child: LogViewer()),
|
||||
],
|
||||
LogViewer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned.fill(child: Testbed()),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -3,8 +3,10 @@ import 'dart:io';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:swift_control/main.dart';
|
||||
import 'package:swift_control/utils/requirements/platform.dart';
|
||||
import 'package:swift_control/widgets/changelog_dialog.dart';
|
||||
import 'package:swift_control/widgets/menu.dart';
|
||||
import 'package:swift_control/widgets/title.dart';
|
||||
|
||||
@@ -30,6 +32,7 @@ class _RequirementsPageState extends State<RequirementsPage> with WidgetsBinding
|
||||
// call after first frame
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
settings.init().then((_) {
|
||||
_checkAndShowChangelog();
|
||||
if (!kIsWeb && Platform.isMacOS) {
|
||||
// add more delay due to CBManagerStateUnknown
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
@@ -48,6 +51,23 @@ class _RequirementsPageState extends State<RequirementsPage> with WidgetsBinding
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _checkAndShowChangelog() async {
|
||||
try {
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
final currentVersion = packageInfo.version;
|
||||
final lastSeenVersion = settings.getLastSeenVersion();
|
||||
|
||||
if (mounted) {
|
||||
await ChangelogDialog.showIfNeeded(context, currentVersion, lastSeenVersion);
|
||||
}
|
||||
|
||||
// Update last seen version
|
||||
await settings.setLastSeenVersion(currentVersion);
|
||||
} catch (e) {
|
||||
print('Failed to check changelog: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:swift_control/main.dart';
|
||||
import 'package:swift_control/widgets/small_progress_indicator.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import '../widgets/logviewer.dart';
|
||||
|
||||
@@ -57,6 +58,14 @@ class _ScanWidgetState extends State<ScanWidget> {
|
||||
Text(
|
||||
'Scanning for devices... Make sure they are powered on and in range and not connected to another device.',
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
launchUrlString(
|
||||
'https://github.com/jonasbark/swiftcontrol/?tab=readme-ov-file#supported-platforms',
|
||||
);
|
||||
},
|
||||
child: const Text("Show Troubleshooting Guide"),
|
||||
),
|
||||
SmallProgressIndicator(),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -8,6 +8,8 @@ import 'package:flutter/services.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:keypress_simulator/keypress_simulator.dart';
|
||||
import 'package:swift_control/main.dart';
|
||||
import 'package:swift_control/widgets/keymap_explanation.dart';
|
||||
import 'package:swift_control/widgets/testbed.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import '../bluetooth/messages/click_notification.dart';
|
||||
@@ -53,6 +55,13 @@ class _TouchAreaSetupPageState extends State<TouchAreaSetupPage> {
|
||||
_actionSubscription.cancel();
|
||||
// Exit full screen
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values);
|
||||
// Reset orientation preferences to allow all orientations
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown,
|
||||
DeviceOrientation.landscapeLeft,
|
||||
DeviceOrientation.landscapeRight,
|
||||
]);
|
||||
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
|
||||
windowManager.setFullScreen(false);
|
||||
}
|
||||
@@ -62,7 +71,13 @@ class _TouchAreaSetupPageState extends State<TouchAreaSetupPage> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky, overlays: []);
|
||||
// Force landscape orientation during keymap editing
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
|
||||
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky, overlays: []).then((_) {
|
||||
// this will make sure the buttons are placed correctly after the transition
|
||||
setState(() {});
|
||||
});
|
||||
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
|
||||
windowManager.setFullScreen(true);
|
||||
}
|
||||
@@ -107,142 +122,166 @@ class _TouchAreaSetupPageState extends State<TouchAreaSetupPage> {
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildDraggableArea({
|
||||
List<Widget> _buildDraggableArea({
|
||||
required Offset position,
|
||||
required bool enableTouch,
|
||||
required void Function(Offset newPosition) onPositionChanged,
|
||||
required Color color,
|
||||
required KeyPair keyPair,
|
||||
required String label,
|
||||
}) {
|
||||
return Positioned(
|
||||
left: position.dx,
|
||||
top: position.dy,
|
||||
child: PopupMenuButton<PhysicalKeyboardKey>(
|
||||
tooltip: 'Drag or click for special keys',
|
||||
itemBuilder:
|
||||
(context) => [
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: null,
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.keyboard_alt_outlined),
|
||||
title: const Text('Simulate Keyboard shortcut'),
|
||||
),
|
||||
onTap: () async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false, // enable Escape key
|
||||
builder:
|
||||
(c) =>
|
||||
HotKeyListenerDialog(customApp: actionHandler.supportedApp! as CustomApp, keyPair: keyPair),
|
||||
);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: null,
|
||||
child: ListTile(title: const Text('Simulate Touch'), leading: Icon(Icons.touch_app_outlined)),
|
||||
onTap: () {
|
||||
keyPair.physicalKey = null;
|
||||
keyPair.logicalKey = null;
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: null,
|
||||
onTap: () {
|
||||
keyPair.isLongPress = !keyPair.isLongPress;
|
||||
setState(() {});
|
||||
},
|
||||
child: CheckboxListTile(
|
||||
value: keyPair.isLongPress,
|
||||
onChanged: (value) {
|
||||
keyPair.isLongPress = value ?? false;
|
||||
setState(() {});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
title: const Text('Long Press Mode (vs. repeating)'),
|
||||
),
|
||||
),
|
||||
PopupMenuDivider(),
|
||||
PopupMenuItem(
|
||||
child: PopupMenuButton<PhysicalKeyboardKey>(
|
||||
padding: EdgeInsets.zero,
|
||||
itemBuilder:
|
||||
(context) => [
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: PhysicalKeyboardKey.mediaPlayPause,
|
||||
child: const Text('Media: Play/Pause'),
|
||||
),
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: PhysicalKeyboardKey.mediaStop,
|
||||
child: const Text('Media: Stop'),
|
||||
),
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: PhysicalKeyboardKey.mediaTrackPrevious,
|
||||
child: const Text('Media: Previous'),
|
||||
),
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: PhysicalKeyboardKey.mediaTrackNext,
|
||||
child: const Text('Media: Next'),
|
||||
),
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: PhysicalKeyboardKey.audioVolumeUp,
|
||||
child: const Text('Media: Volume Up'),
|
||||
),
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: PhysicalKeyboardKey.audioVolumeDown,
|
||||
child: const Text('Media: Volume Down'),
|
||||
),
|
||||
],
|
||||
onSelected: (key) {
|
||||
keyPair.physicalKey = key;
|
||||
keyPair.logicalKey = null;
|
||||
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)
|
||||
? (flutterView.display.size.height - flutterView.physicalSize.height) / flutterView.devicePixelRatio
|
||||
: 0.0;
|
||||
|
||||
if (kDebugMode) {
|
||||
print('Display Size: ${flutterView.display.size}');
|
||||
print('View size: ${flutterView.physicalSize}');
|
||||
print('Difference: $differenceInHeight');
|
||||
}
|
||||
return [
|
||||
Positioned(
|
||||
left: position.dx,
|
||||
top: position.dy - differenceInHeight,
|
||||
child: PopupMenuButton<PhysicalKeyboardKey>(
|
||||
enabled: enableTouch,
|
||||
tooltip: 'Drag to reposition. Tap to edit.',
|
||||
itemBuilder:
|
||||
(context) => [
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: null,
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.keyboard_alt_outlined),
|
||||
title: const Text('Simulate Keyboard shortcut'),
|
||||
),
|
||||
onTap: () async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false, // enable Escape key
|
||||
builder:
|
||||
(c) => HotKeyListenerDialog(
|
||||
customApp: actionHandler.supportedApp! as CustomApp,
|
||||
keyPair: keyPair,
|
||||
),
|
||||
);
|
||||
setState(() {});
|
||||
},
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.music_note_outlined),
|
||||
trailing: Icon(Icons.arrow_right),
|
||||
title: Text('Simulate Media key'),
|
||||
),
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: null,
|
||||
child: ListTile(title: const Text('Simulate Touch'), leading: Icon(Icons.touch_app_outlined)),
|
||||
onTap: () {
|
||||
keyPair.physicalKey = null;
|
||||
keyPair.logicalKey = null;
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: null,
|
||||
onTap: () {
|
||||
keyPair.isLongPress = !keyPair.isLongPress;
|
||||
setState(() {});
|
||||
},
|
||||
child: CheckboxListTile(
|
||||
value: keyPair.isLongPress,
|
||||
onChanged: (value) {
|
||||
keyPair.isLongPress = value ?? false;
|
||||
setState(() {});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
title: const Text('Long Press Mode (vs. repeating)'),
|
||||
),
|
||||
),
|
||||
),
|
||||
PopupMenuDivider(),
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: null,
|
||||
child: ListTile(title: const Text('Delete Keymap'), leading: Icon(Icons.delete, color: Colors.red)),
|
||||
onTap: () {
|
||||
actionHandler.supportedApp!.keymap.keyPairs.remove(keyPair);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
onSelected: (key) {
|
||||
keyPair.physicalKey = key;
|
||||
keyPair.logicalKey = null;
|
||||
setState(() {});
|
||||
},
|
||||
child: Container(
|
||||
color: kDebugMode && false ? Colors.yellow : null,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Draggable(
|
||||
feedback: Material(
|
||||
color: Colors.transparent,
|
||||
child: _TouchDot(color: Colors.yellow, label: label, keyPair: keyPair),
|
||||
PopupMenuDivider(),
|
||||
PopupMenuItem(
|
||||
child: PopupMenuButton<PhysicalKeyboardKey>(
|
||||
padding: EdgeInsets.zero,
|
||||
itemBuilder:
|
||||
(context) => [
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: PhysicalKeyboardKey.mediaPlayPause,
|
||||
child: const Text('Media: Play/Pause'),
|
||||
),
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: PhysicalKeyboardKey.mediaStop,
|
||||
child: const Text('Media: Stop'),
|
||||
),
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: PhysicalKeyboardKey.mediaTrackPrevious,
|
||||
child: const Text('Media: Previous'),
|
||||
),
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: PhysicalKeyboardKey.mediaTrackNext,
|
||||
child: const Text('Media: Next'),
|
||||
),
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: PhysicalKeyboardKey.audioVolumeUp,
|
||||
child: const Text('Media: Volume Up'),
|
||||
),
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: PhysicalKeyboardKey.audioVolumeDown,
|
||||
child: const Text('Media: Volume Down'),
|
||||
),
|
||||
],
|
||||
onSelected: (key) {
|
||||
keyPair.physicalKey = key;
|
||||
keyPair.logicalKey = null;
|
||||
|
||||
setState(() {});
|
||||
},
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.music_note_outlined),
|
||||
trailing: Icon(Icons.arrow_right),
|
||||
title: Text('Simulate Media key'),
|
||||
),
|
||||
),
|
||||
),
|
||||
childWhenDragging: const SizedBox.shrink(),
|
||||
onDraggableCanceled: (_, offset) {
|
||||
setState(() => onPositionChanged(offset));
|
||||
},
|
||||
child: _TouchDot(color: color, label: label, keyPair: keyPair),
|
||||
),
|
||||
],
|
||||
PopupMenuDivider(),
|
||||
PopupMenuItem<PhysicalKeyboardKey>(
|
||||
value: null,
|
||||
child: ListTile(title: const Text('Delete Keymap'), leading: Icon(Icons.delete, color: Colors.red)),
|
||||
onTap: () {
|
||||
actionHandler.supportedApp!.keymap.keyPairs.remove(keyPair);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
onSelected: (key) {
|
||||
keyPair.physicalKey = key;
|
||||
keyPair.logicalKey = null;
|
||||
setState(() {});
|
||||
},
|
||||
child: Draggable(
|
||||
feedback: Material(color: Colors.transparent, child: KeypairExplanation(withKey: true, keyPair: keyPair)),
|
||||
childWhenDragging: const SizedBox.shrink(),
|
||||
onDraggableCanceled: (_, offset) {
|
||||
final fixedPosition = offset + Offset(0, differenceInHeight);
|
||||
setState(() => onPositionChanged(fixedPosition));
|
||||
},
|
||||
child: KeypairExplanation(withKey: true, keyPair: keyPair),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (!keyPair.isSpecialKey && keyPair.physicalKey == null && keyPair.touchPosition != Offset.zero)
|
||||
Positioned(
|
||||
left: position.dx - 10,
|
||||
top: position.dy - 10 - differenceInHeight,
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
size: 20,
|
||||
shadows: [
|
||||
Shadow(color: Colors.white, offset: Offset(1, 1)),
|
||||
Shadow(color: Colors.white, offset: Offset(-1, -1)),
|
||||
Shadow(color: Colors.white, offset: Offset(-1, 1)),
|
||||
Shadow(color: Colors.white, offset: Offset(-1, 1)),
|
||||
Shadow(color: Colors.white, offset: Offset(1, -1)),
|
||||
],
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -262,12 +301,12 @@ class _TouchAreaSetupPageState extends State<TouchAreaSetupPage> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Text('''1. Create an in-game screenshot of your app (e.g. within MyWhoosh)
|
||||
Text('''1. Create an in-game screenshot of your app (e.g. within MyWhoosh) in landscape orientation
|
||||
2. Load the screenshot with the button below
|
||||
3. Make sure the app is in the correct orientation (portrait or landscape)
|
||||
3. The app is automatically set to landscape orientation for accurate mapping
|
||||
4. Press a button on your Zwift device to create a touch area
|
||||
5. Drag the touch areas to the desired position on the screenshot
|
||||
5. Save and close this screen'''),
|
||||
6. Save and close this screen'''),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
_pickScreenshot();
|
||||
@@ -279,24 +318,26 @@ class _TouchAreaSetupPageState extends State<TouchAreaSetupPage> {
|
||||
),
|
||||
),
|
||||
// Touch Areas
|
||||
...?actionHandler.supportedApp?.keymap.keyPairs.map(
|
||||
(keyPair) => _buildDraggableArea(
|
||||
position: Offset(
|
||||
keyPair.touchPosition.dx / devicePixelRatio - touchAreaSize / 2,
|
||||
keyPair.touchPosition.dy / devicePixelRatio - touchAreaSize / 2 - (isDesktop ? touchAreaSize * 1.5 : 0),
|
||||
),
|
||||
keyPair: keyPair,
|
||||
onPositionChanged: (newPos) {
|
||||
final converted =
|
||||
newPos.translate(touchAreaSize / 2, touchAreaSize / 2 + (isDesktop ? touchAreaSize * 1.5 : 0)) *
|
||||
devicePixelRatio;
|
||||
keyPair.touchPosition = converted;
|
||||
setState(() {});
|
||||
},
|
||||
color: Colors.red,
|
||||
label: keyPair.buttons.joinToString(transform: (e) => e.name, separator: '\n'),
|
||||
),
|
||||
),
|
||||
...?actionHandler.supportedApp?.keymap.keyPairs
|
||||
.map(
|
||||
(keyPair) => _buildDraggableArea(
|
||||
enableTouch: true,
|
||||
position: Offset(
|
||||
keyPair.touchPosition.dx / devicePixelRatio,
|
||||
keyPair.touchPosition.dy / devicePixelRatio,
|
||||
),
|
||||
keyPair: keyPair,
|
||||
onPositionChanged: (newPos) {
|
||||
final converted = newPos * devicePixelRatio;
|
||||
keyPair.touchPosition = converted;
|
||||
setState(() {});
|
||||
},
|
||||
color: Colors.red,
|
||||
),
|
||||
)
|
||||
.flatten(),
|
||||
|
||||
Positioned.fill(child: Testbed()),
|
||||
|
||||
Positioned(
|
||||
top: 40,
|
||||
@@ -304,15 +345,20 @@ class _TouchAreaSetupPageState extends State<TouchAreaSetupPage> {
|
||||
child: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
actionHandler.supportedApp?.keymap.reset();
|
||||
setState(() {});
|
||||
},
|
||||
icon: const Icon(Icons.lock_reset),
|
||||
label: Text('Reset'),
|
||||
),
|
||||
ElevatedButton.icon(onPressed: _saveAndClose, icon: const Icon(Icons.save), label: const Text("Save")),
|
||||
PopupMenuButton(
|
||||
itemBuilder:
|
||||
(c) => [
|
||||
PopupMenuItem(
|
||||
child: Text('Reset'),
|
||||
onTap: () {
|
||||
actionHandler.supportedApp?.keymap.reset();
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
icon: Icon(Icons.more_vert),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -322,59 +368,46 @@ class _TouchAreaSetupPageState extends State<TouchAreaSetupPage> {
|
||||
}
|
||||
}
|
||||
|
||||
class _TouchDot extends StatelessWidget {
|
||||
final Color color;
|
||||
final String label;
|
||||
class KeypairExplanation extends StatelessWidget {
|
||||
final bool withKey;
|
||||
final KeyPair keyPair;
|
||||
|
||||
const _TouchDot({required this.color, required this.label, required this.keyPair});
|
||||
const KeypairExplanation({super.key, required this.keyPair, this.withKey = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
return Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
Container(
|
||||
width: touchAreaSize,
|
||||
height: touchAreaSize,
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.6),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: keyPair.isLongPress ? Colors.green : Colors.black,
|
||||
width: keyPair.isLongPress ? 3 : 2,
|
||||
),
|
||||
if (withKey) KeyWidget(label: keyPair.buttons.joinToString(transform: (e) => e.name, separator: '\n')),
|
||||
if (keyPair.physicalKey != null) ...[
|
||||
Icon(switch (keyPair.physicalKey) {
|
||||
PhysicalKeyboardKey.mediaPlayPause ||
|
||||
PhysicalKeyboardKey.mediaStop ||
|
||||
PhysicalKeyboardKey.mediaTrackPrevious ||
|
||||
PhysicalKeyboardKey.mediaTrackNext ||
|
||||
PhysicalKeyboardKey.audioVolumeUp ||
|
||||
PhysicalKeyboardKey.audioVolumeDown => Icons.music_note_outlined,
|
||||
_ => Icons.keyboard,
|
||||
}, size: 16),
|
||||
KeyWidget(
|
||||
label: switch (keyPair.physicalKey) {
|
||||
PhysicalKeyboardKey.mediaPlayPause => 'Media: Play/Pause',
|
||||
PhysicalKeyboardKey.mediaStop => 'Media: Stop',
|
||||
PhysicalKeyboardKey.mediaTrackPrevious => 'Media: Previous',
|
||||
PhysicalKeyboardKey.mediaTrackNext => 'Media: Next',
|
||||
PhysicalKeyboardKey.audioVolumeUp => 'Media: Volume Up',
|
||||
PhysicalKeyboardKey.audioVolumeDown => 'Media: Volume Down',
|
||||
_ => keyPair.logicalKey?.keyLabel ?? 'Unknown',
|
||||
},
|
||||
),
|
||||
child: Icon(
|
||||
keyPair.isSpecialKey
|
||||
? Icons.music_note_outlined
|
||||
: keyPair.physicalKey != null
|
||||
? Icons.keyboard_alt_outlined
|
||||
: Icons.touch_app_outlined,
|
||||
),
|
||||
),
|
||||
if (keyPair.isLongPress) Text('using long press'),
|
||||
] else ...[
|
||||
Icon(Icons.touch_app, size: 16),
|
||||
KeyWidget(label: 'X: ${keyPair.touchPosition.dx.toInt()}, Y: ${keyPair.touchPosition.dy.toInt()}'),
|
||||
|
||||
Container(
|
||||
color: Colors.white.withAlpha(180),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label, style: TextStyle(color: Colors.black, fontSize: 12)),
|
||||
if (keyPair.physicalKey != null)
|
||||
Text(switch (keyPair.physicalKey) {
|
||||
PhysicalKeyboardKey.mediaPlayPause => 'Media: Play/Pause',
|
||||
PhysicalKeyboardKey.mediaStop => 'Media: Stop',
|
||||
PhysicalKeyboardKey.mediaTrackPrevious => 'Media: Previous',
|
||||
PhysicalKeyboardKey.mediaTrackNext => 'Media: Next',
|
||||
PhysicalKeyboardKey.audioVolumeUp => 'Media: Volume Up',
|
||||
PhysicalKeyboardKey.audioVolumeDown => 'Media: Volume Down',
|
||||
_ => keyPair.logicalKey?.keyLabel ?? 'Unknown',
|
||||
}, style: TextStyle(color: Colors.black87, fontSize: 12)),
|
||||
if (keyPair.isLongPress)
|
||||
Text('Long Press', style: TextStyle(color: Colors.green, fontSize: 10, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (keyPair.isLongPress) Text('using long press'),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:swift_control/main.dart';
|
||||
import 'package:swift_control/utils/actions/base_actions.dart';
|
||||
import 'package:swift_control/utils/keymap/apps/custom_app.dart';
|
||||
import 'package:swift_control/utils/keymap/buttons.dart';
|
||||
import 'package:swift_control/widgets/keymap_explanation.dart';
|
||||
|
||||
import '../keymap/apps/supported_app.dart';
|
||||
import '../single_line_exception.dart';
|
||||
@@ -24,7 +25,7 @@ class AndroidActions extends BaseActions {
|
||||
@override
|
||||
Future<String> performAction(ZwiftButton button, {bool isKeyDown = true, bool isKeyUp = false}) async {
|
||||
if (supportedApp == null) {
|
||||
return ("Could not perform ${button.name}: No keymap set");
|
||||
return ("Could not perform ${button.name.splitByUpperCase()}: No keymap set");
|
||||
}
|
||||
|
||||
if (supportedApp is CustomApp) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:keypress_simulator/keypress_simulator.dart';
|
||||
import 'package:swift_control/utils/actions/base_actions.dart';
|
||||
import 'package:swift_control/utils/keymap/buttons.dart';
|
||||
import 'package:swift_control/widgets/keymap_explanation.dart';
|
||||
|
||||
class DesktopActions extends BaseActions {
|
||||
// Track keys that are currently held down in long press mode
|
||||
@@ -14,7 +15,7 @@ class DesktopActions extends BaseActions {
|
||||
|
||||
final keyPair = supportedApp!.keymap.getKeyPair(action);
|
||||
if (keyPair == null) {
|
||||
return ('Keymap entry not found for action: $action');
|
||||
return ('Keymap entry not found for action: ${action.toString().splitByUpperCase()}');
|
||||
}
|
||||
|
||||
// Handle long press mode
|
||||
@@ -53,12 +54,12 @@ class DesktopActions extends BaseActions {
|
||||
if (keyPair.physicalKey != null) {
|
||||
await keyPressSimulator.simulateKeyDown(keyPair.physicalKey);
|
||||
await keyPressSimulator.simulateKeyUp(keyPair.physicalKey);
|
||||
return 'Key pressed: ${keyPair.logicalKey?.keyLabel}';
|
||||
return 'Key pressed: $keyPair';
|
||||
} else {
|
||||
final point = supportedApp!.resolveTouchPosition(action: action, windowInfo: null);
|
||||
await keyPressSimulator.simulateMouseClickDown(point);
|
||||
await keyPressSimulator.simulateMouseClickUp(point);
|
||||
return 'Mouse clicked at: $point';
|
||||
return 'Mouse clicked at: ${point.dx} ${point.dy}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
79
lib/utils/changelog.dart
Normal file
79
lib/utils/changelog.dart
Normal file
@@ -0,0 +1,79 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class ChangelogEntry {
|
||||
final String version;
|
||||
final String date;
|
||||
final List<String> changes;
|
||||
|
||||
ChangelogEntry({
|
||||
required this.version,
|
||||
required this.date,
|
||||
required this.changes,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '### $version ($date)\n${changes.map((c) => '- $c').join('\n')}';
|
||||
}
|
||||
}
|
||||
|
||||
class ChangelogParser {
|
||||
static Future<List<ChangelogEntry>> parse() async {
|
||||
final content = await rootBundle.loadString('CHANGELOG.md');
|
||||
return parseContent(content);
|
||||
}
|
||||
|
||||
static List<ChangelogEntry> parseContent(String content) {
|
||||
final entries = <ChangelogEntry>[];
|
||||
final lines = content.split('\n');
|
||||
|
||||
ChangelogEntry? currentEntry;
|
||||
|
||||
for (var line in lines) {
|
||||
// Check if this is a version header (e.g., "### 2.6.0 (2025-09-28)")
|
||||
if (line.startsWith('### ')) {
|
||||
// Save previous entry if exists
|
||||
if (currentEntry != null) {
|
||||
entries.add(currentEntry);
|
||||
}
|
||||
|
||||
// Parse new entry
|
||||
final header = line.substring(4).trim();
|
||||
final match = RegExp(r'^(\S+)\s+\(([^)]+)\)').firstMatch(header);
|
||||
if (match != null) {
|
||||
currentEntry = ChangelogEntry(
|
||||
version: match.group(1)!,
|
||||
date: match.group(2)!,
|
||||
changes: [],
|
||||
);
|
||||
}
|
||||
} else if (line.startsWith('- ') && currentEntry != null) {
|
||||
// Add change to current entry
|
||||
currentEntry.changes.add(line.substring(2).trim());
|
||||
} else if (line.startsWith(' - ') && currentEntry != null) {
|
||||
// Sub-bullet point
|
||||
currentEntry.changes.add(line.substring(4).trim());
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last entry
|
||||
if (currentEntry != null) {
|
||||
entries.add(currentEntry);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
static Future<ChangelogEntry?> getLatestEntry() async {
|
||||
final entries = await parse();
|
||||
return entries.isNotEmpty ? entries.first : null;
|
||||
}
|
||||
|
||||
static Future<String?> getLatestEntryForPlayStore() async {
|
||||
final entry = await getLatestEntry();
|
||||
if (entry == null) return null;
|
||||
|
||||
// Format for Play Store: just the changes, no version header
|
||||
return entry.changes.join('\n');
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ class CustomApp extends SupportedApp {
|
||||
Offset resolveTouchPosition({required ZwiftButton action, required WindowEvent? windowInfo}) {
|
||||
final keyPair = keymap.getKeyPair(action);
|
||||
if (keyPair == null || keyPair.touchPosition == Offset.zero) {
|
||||
throw SingleLineException("No key pair found for action: $action");
|
||||
throw SingleLineException("No key pair found for action: $action. You may want to adjust the keymap.");
|
||||
}
|
||||
return keyPair.touchPosition;
|
||||
}
|
||||
|
||||
@@ -143,10 +143,7 @@ class NotificationRequirement extends PlatformRequirement {
|
||||
InitializationSettings(android: initializationSettingsAndroid),
|
||||
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
|
||||
onDidReceiveNotificationResponse: (n) {
|
||||
if (n.actionId != null) {
|
||||
connection.reset();
|
||||
exit(0);
|
||||
}
|
||||
notificationTapBackground(n);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -188,6 +185,8 @@ class NotificationRequirement extends PlatformRequirement {
|
||||
void notificationTapBackground(NotificationResponse notificationResponse) {
|
||||
if (notificationResponse.actionId != null) {
|
||||
connection.reset();
|
||||
exit(0);
|
||||
AndroidFlutterLocalNotificationsPlugin().stopForegroundService().then((_) {
|
||||
exit(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ Future<List<PlatformRequirement>> getRequirements() async {
|
||||
List<PlatformRequirement> list;
|
||||
if (kIsWeb) {
|
||||
list = [BluetoothTurnedOn(), BluetoothScanning()];
|
||||
} else if (Platform.isMacOS) {
|
||||
} else if (Platform.isMacOS || Platform.isIOS) {
|
||||
list = [BluetoothTurnedOn(), KeyboardRequirement(), BluetoothScanning()];
|
||||
} else if (Platform.isWindows) {
|
||||
list = [BluetoothTurnedOn(), KeyboardRequirement(), BluetoothScanning()];
|
||||
|
||||
@@ -32,10 +32,31 @@ class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
void setApp(SupportedApp app) {
|
||||
Future<void> reset() async {
|
||||
await _prefs.clear();
|
||||
actionHandler.init(null);
|
||||
}
|
||||
|
||||
Future<void> setApp(SupportedApp app) async {
|
||||
if (app is CustomApp) {
|
||||
_prefs.setStringList("customapp", app.encodeKeymap());
|
||||
await _prefs.setStringList("customapp", app.encodeKeymap());
|
||||
}
|
||||
_prefs.setString('app', app.name);
|
||||
await _prefs.setString('app', app.name);
|
||||
}
|
||||
|
||||
String? getLastSeenVersion() {
|
||||
return _prefs.getString('last_seen_version');
|
||||
}
|
||||
|
||||
Future<void> setLastSeenVersion(String version) async {
|
||||
await _prefs.setString('last_seen_version', version);
|
||||
}
|
||||
|
||||
bool getVibrationEnabled() {
|
||||
return _prefs.getBool('vibration_enabled') ?? true;
|
||||
}
|
||||
|
||||
Future<void> setVibrationEnabled(bool enabled) async {
|
||||
await _prefs.setBool('vibration_enabled', enabled);
|
||||
}
|
||||
}
|
||||
|
||||
59
lib/widgets/changelog_dialog.dart
Normal file
59
lib/widgets/changelog_dialog.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:swift_control/utils/changelog.dart';
|
||||
|
||||
class ChangelogDialog extends StatelessWidget {
|
||||
final ChangelogEntry entry;
|
||||
|
||||
const ChangelogDialog({super.key, required this.entry});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('What\'s New'),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'Version ${entry.version}',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.normal),
|
||||
),
|
||||
],
|
||||
),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children:
|
||||
entry.changes
|
||||
.map(
|
||||
(change) => Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('• ', style: TextStyle(fontSize: 16)),
|
||||
Expanded(child: Text(change, style: Theme.of(context).textTheme.bodyMedium)),
|
||||
],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
actions: [TextButton(onPressed: () => Navigator.of(context).pop(), child: Text('Got it!'))],
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> showIfNeeded(BuildContext context, String currentVersion, String? lastSeenVersion) async {
|
||||
// Show dialog if this is a new version
|
||||
if (lastSeenVersion != currentVersion) {
|
||||
try {
|
||||
final entry = await ChangelogParser.getLatestEntry();
|
||||
if (entry != null && context.mounted) {
|
||||
showDialog(context: context, builder: (context) => ChangelogDialog(entry: entry));
|
||||
}
|
||||
} catch (e) {
|
||||
print('Failed to load changelog for dialog: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:swift_control/main.dart';
|
||||
import 'package:swift_control/utils/keymap/keymap.dart';
|
||||
|
||||
import '../pages/touch_area.dart';
|
||||
|
||||
class KeymapExplanation extends StatelessWidget {
|
||||
final Keymap keymap;
|
||||
final VoidCallback onUpdate;
|
||||
@@ -18,10 +20,10 @@ class KeymapExplanation extends StatelessWidget {
|
||||
|
||||
final keyboardGroups = availableKeypairs
|
||||
.filter((e) => e.physicalKey != null)
|
||||
.groupBy((element) => '${element.physicalKey}-${element.isLongPress}');
|
||||
.groupBy((element) => '${element.physicalKey?.usbHidUsage}-${element.isLongPress}');
|
||||
final touchGroups = availableKeypairs
|
||||
.filter((e) => e.physicalKey == null && e.touchPosition != Offset.zero)
|
||||
.groupBy((element) => '${element.touchPosition}-${element.isLongPress}');
|
||||
.groupBy((element) => '${element.touchPosition.dx}-${element.touchPosition.dy}-${element.isLongPress}');
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -64,21 +66,11 @@ class KeymapExplanation extends StatelessWidget {
|
||||
for (final keyPair in pair.value)
|
||||
for (final button in keyPair.buttons)
|
||||
if (connectedDevice?.availableButtons.contains(button) == true)
|
||||
IntrinsicWidth(child: _KeyWidget(label: button.name.splitByUpperCase())),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(Icons.keyboard, size: 16),
|
||||
_KeyWidget(label: pair.value.first.logicalKey?.keyLabel ?? ''),
|
||||
if (pair.value.first.isLongPress) Text('using long press'),
|
||||
IntrinsicWidth(child: KeyWidget(label: button.name)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(padding: const EdgeInsets.all(6), child: KeypairExplanation(keyPair: pair.value.first)),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -93,25 +85,11 @@ class KeymapExplanation extends StatelessWidget {
|
||||
for (final keyPair in pair.value)
|
||||
for (final button in keyPair.buttons)
|
||||
if (connectedDevice?.availableButtons.contains(button) == true)
|
||||
_KeyWidget(label: button.name.splitByUpperCase()),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(Icons.touch_app, size: 16),
|
||||
_KeyWidget(
|
||||
label:
|
||||
'x: ${pair.value.first.touchPosition.dx.toInt()}, y: ${pair.value.first.touchPosition.dy.toInt()}',
|
||||
),
|
||||
|
||||
if (pair.value.first.isLongPress) Text('using long press'),
|
||||
KeyWidget(label: button.name),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(padding: const EdgeInsets.all(6), child: KeypairExplanation(keyPair: pair.value.first)),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -122,9 +100,9 @@ class KeymapExplanation extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _KeyWidget extends StatelessWidget {
|
||||
class KeyWidget extends StatelessWidget {
|
||||
final String label;
|
||||
const _KeyWidget({super.key, required this.label});
|
||||
const KeyWidget({super.key, required this.label});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -138,7 +116,7 @@ class _KeyWidget extends StatelessWidget {
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
label,
|
||||
label.splitByUpperCase(),
|
||||
style: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 12,
|
||||
@@ -150,7 +128,7 @@ class _KeyWidget extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
extension on String {
|
||||
extension SplitByUppercase on String {
|
||||
String splitByUpperCase() {
|
||||
return replaceAllMapped(RegExp(r'([a-z])([A-Z])'), (match) => '${match.group(1)} ${match.group(2)}').capitalize();
|
||||
}
|
||||
|
||||
@@ -50,42 +50,38 @@ class _LogviewerState extends State<LogViewer> {
|
||||
Widget build(BuildContext context) {
|
||||
return SelectionArea(
|
||||
child: ListView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: _scrollController,
|
||||
children: [
|
||||
..._actions.map(
|
||||
(action) => Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: action.date.toString().split(" ").last,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFeatures: [FontFeature.tabularFigures()],
|
||||
fontFamily: "monospace",
|
||||
fontFamilyFallback: <String>["Courier"],
|
||||
shrinkWrap: true,
|
||||
reverse: true,
|
||||
children:
|
||||
_actions
|
||||
.map(
|
||||
(action) => Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: action.date.toString().split(" ").last,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFeatures: [FontFeature.tabularFigures()],
|
||||
fontFamily: "monospace",
|
||||
fontFamilyFallback: <String>["Courier"],
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: " ${action.entry}",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFeatures: [FontFeature.tabularFigures()],
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: " ${action.entry}",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFeatures: [FontFeature.tabularFigures()],
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_actions.clear();
|
||||
setState(() {});
|
||||
},
|
||||
child: Text('Clear Log'),
|
||||
),
|
||||
],
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:swift_control/utils/keymap/buttons.dart';
|
||||
import 'package:swift_control/widgets/title.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import '../pages/changelog.dart';
|
||||
import '../pages/device.dart';
|
||||
|
||||
List<Widget> buildMenuButtons() {
|
||||
@@ -58,25 +59,45 @@ class MenuButton extends StatelessWidget {
|
||||
itemBuilder:
|
||||
(c) => [
|
||||
if (kDebugMode) ...[
|
||||
...ZwiftButton.values.map(
|
||||
(e) => PopupMenuItem(
|
||||
child: Text(e.name),
|
||||
onTap: () {
|
||||
Future.delayed(Duration(seconds: 2)).then((_) {
|
||||
actionHandler.performAction(e);
|
||||
});
|
||||
PopupMenuItem(
|
||||
child: PopupMenuButton(
|
||||
child: Text("Simulate buttons"),
|
||||
itemBuilder: (_) {
|
||||
return ZwiftButton.values
|
||||
.map(
|
||||
(e) => PopupMenuItem(
|
||||
child: Text(e.name),
|
||||
onTap: () {
|
||||
Future.delayed(Duration(seconds: 2)).then((_) {
|
||||
actionHandler.performAction(e);
|
||||
});
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
},
|
||||
),
|
||||
),
|
||||
PopupMenuItem(child: PopupMenuDivider()),
|
||||
PopupMenuItem(
|
||||
child: Text('Continue'),
|
||||
onTap: () {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (c) => DevicePage()));
|
||||
},
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Text('Reset'),
|
||||
onTap: () async {
|
||||
await settings.reset();
|
||||
},
|
||||
),
|
||||
PopupMenuItem(child: PopupMenuDivider()),
|
||||
],
|
||||
PopupMenuItem(
|
||||
child: Text('Changelog'),
|
||||
onTap: () {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (c) => ChangelogPage()));
|
||||
},
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Text('Feedback'),
|
||||
onTap: () {
|
||||
|
||||
323
lib/widgets/testbed.dart
Normal file
323
lib/widgets/testbed.dart
Normal file
@@ -0,0 +1,323 @@
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// A developer overlay that visualizes touches and keyboard events.
|
||||
/// - Touch dots appear where you touch and fade out over [touchRevealDuration].
|
||||
/// - Keyboard events are listed temporarily and fade out over [keyboardRevealDuration].
|
||||
class Testbed extends StatefulWidget {
|
||||
const Testbed({
|
||||
super.key,
|
||||
this.enabled = true,
|
||||
this.showTouches = true,
|
||||
this.showKeyboard = true,
|
||||
this.touchRevealDuration = const Duration(seconds: 2),
|
||||
this.keyboardRevealDuration = const Duration(seconds: 2),
|
||||
this.maxKeyboardEvents = 6,
|
||||
this.touchColor = const Color(0xFF00BCD4), // cyan-ish
|
||||
this.keyboardBadgeColor = const Color(0xCC000000), // translucent black
|
||||
this.keyboardTextStyle = const TextStyle(color: Colors.white, fontSize: 12),
|
||||
});
|
||||
|
||||
final bool enabled;
|
||||
final bool showTouches;
|
||||
final bool showKeyboard;
|
||||
|
||||
final Duration touchRevealDuration;
|
||||
final Duration keyboardRevealDuration;
|
||||
final int maxKeyboardEvents;
|
||||
|
||||
final Color touchColor;
|
||||
final Color keyboardBadgeColor;
|
||||
final TextStyle keyboardTextStyle;
|
||||
|
||||
@override
|
||||
State<Testbed> createState() => _TestbedState();
|
||||
}
|
||||
|
||||
class _TestbedState extends State<Testbed> with SingleTickerProviderStateMixin {
|
||||
late final Ticker _ticker;
|
||||
|
||||
// ----- Touch tracking -----
|
||||
final Map<int, _TouchSample> _active = <int, _TouchSample>{};
|
||||
final List<_TouchSample> _history = <_TouchSample>[];
|
||||
|
||||
// ----- Keyboard tracking -----
|
||||
final List<_KeySample> _keys = <_KeySample>[];
|
||||
|
||||
// Focus to receive key events without stealing focus from inputs.
|
||||
late final FocusNode _focusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_focusNode = FocusNode(debugLabel: 'TestbedFocus', canRequestFocus: true, skipTraversal: true);
|
||||
|
||||
_ticker = createTicker((_) {
|
||||
// Cull expired touch and key samples.
|
||||
final now = DateTime.now();
|
||||
_history.removeWhere((s) => now.difference(s.timestamp) > widget.touchRevealDuration);
|
||||
_keys.removeWhere((k) => now.difference(k.timestamp) > widget.keyboardRevealDuration);
|
||||
|
||||
if (mounted) setState(() {});
|
||||
})..start();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ticker.dispose();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onPointerDown(PointerDownEvent e) {
|
||||
if (!widget.enabled ||
|
||||
!widget.showTouches ||
|
||||
(e.kind != PointerDeviceKind.unknown && e.kind != PointerDeviceKind.mouse)) {
|
||||
return;
|
||||
}
|
||||
final sample = _TouchSample(
|
||||
pointer: e.pointer,
|
||||
position: e.position,
|
||||
timestamp: DateTime.now(),
|
||||
phase: _TouchPhase.down,
|
||||
);
|
||||
_active[e.pointer] = sample;
|
||||
_history.add(sample);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _onPointerCancel(PointerCancelEvent e) {
|
||||
if (!widget.enabled || !widget.showTouches || !mounted) return;
|
||||
_active.remove(e.pointer);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
KeyEventResult _onKey(FocusNode node, KeyEvent event) {
|
||||
if (!widget.enabled || !widget.showKeyboard || event is KeyUpEvent) return KeyEventResult.ignored;
|
||||
|
||||
final label = event.logicalKey.keyLabel;
|
||||
final keyName = label.isNotEmpty ? label : event.logicalKey.debugName ?? 'Key';
|
||||
final isDown = event is KeyDownEvent;
|
||||
final isUp = event is KeyUpEvent;
|
||||
|
||||
// Filter out repeat KeyDowns if desired (optional).
|
||||
// Here we keep them; comment this block in to drop repeats:
|
||||
// if (event.repeat) return KeyEventResult.handled;
|
||||
|
||||
final sample = _KeySample(
|
||||
text:
|
||||
'${isDown
|
||||
? "↓"
|
||||
: isUp
|
||||
? "↑"
|
||||
: "•"} $keyName',
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
_keys.insert(0, sample);
|
||||
if (_keys.length > widget.maxKeyboardEvents) {
|
||||
_keys.removeLast();
|
||||
}
|
||||
setState(() {});
|
||||
// We don't want to prevent normal text input, so we return ignored.
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Listener(
|
||||
onPointerDown: _onPointerDown,
|
||||
onPointerCancel: _onPointerCancel,
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: Focus(
|
||||
focusNode: _focusNode,
|
||||
autofocus: true,
|
||||
canRequestFocus: true,
|
||||
descendantsAreFocusable: true,
|
||||
onKeyEvent: _onKey,
|
||||
child: Stack(
|
||||
fit: StackFit.passthrough,
|
||||
children: [
|
||||
if (widget.showTouches)
|
||||
Positioned.fill(
|
||||
child: IgnorePointer(
|
||||
child: CustomPaint(
|
||||
painter: _TouchesPainter(
|
||||
now: DateTime.now(),
|
||||
samples: _history,
|
||||
duration: widget.touchRevealDuration,
|
||||
color: widget.touchColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.showKeyboard)
|
||||
Positioned(
|
||||
left: 12,
|
||||
bottom: 12,
|
||||
child: IgnorePointer(
|
||||
child: _KeyboardOverlay(
|
||||
items: _keys,
|
||||
duration: widget.keyboardRevealDuration,
|
||||
badgeColor: widget.keyboardBadgeColor,
|
||||
textStyle: widget.keyboardTextStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Touches =====
|
||||
|
||||
enum _TouchPhase { down, move, up }
|
||||
|
||||
class _TouchSample {
|
||||
_TouchSample({required this.pointer, required this.position, required this.timestamp, required this.phase});
|
||||
|
||||
final int pointer;
|
||||
final Offset position;
|
||||
final DateTime timestamp;
|
||||
final _TouchPhase phase;
|
||||
}
|
||||
|
||||
class _TouchesPainter extends CustomPainter {
|
||||
_TouchesPainter({required this.now, required this.samples, required this.duration, required this.color});
|
||||
|
||||
final DateTime now;
|
||||
final List<_TouchSample> samples;
|
||||
final Duration duration;
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint =
|
||||
Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2;
|
||||
|
||||
for (final s in samples) {
|
||||
final age = now.difference(s.timestamp);
|
||||
if (age > duration) continue;
|
||||
|
||||
final t = age.inMilliseconds / duration.inMilliseconds.clamp(1, 1 << 30);
|
||||
final fade = (1.0 - t).clamp(0.0, 1.0);
|
||||
|
||||
// Two concentric circles: inner filled pulse + outer ring.
|
||||
final baseRadius = 22.0;
|
||||
final pulse = 1.0 + 0.5 * math.sin(t * math.pi); // subtle pulsing
|
||||
final rOuter = baseRadius * (1.0 + 0.35 * t);
|
||||
final rInner = baseRadius * 0.5 * pulse;
|
||||
|
||||
// Outer ring (stroke, fading)
|
||||
paint
|
||||
..style = PaintingStyle.stroke
|
||||
..color = color.withOpacity(0.35 * fade);
|
||||
canvas.drawCircle(s.position, rOuter, paint);
|
||||
|
||||
// Inner fill (stronger)
|
||||
final fill =
|
||||
Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = color.withOpacity(0.35 + 0.35 * fade);
|
||||
canvas.drawCircle(s.position, rInner, fill);
|
||||
|
||||
// Tiny center dot for precision
|
||||
final center =
|
||||
Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = color.withOpacity(0.9 * fade);
|
||||
canvas.drawCircle(s.position, 2.5, center);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _TouchesPainter oldDelegate) {
|
||||
return oldDelegate.now != now ||
|
||||
oldDelegate.samples != samples ||
|
||||
oldDelegate.duration != duration ||
|
||||
oldDelegate.color != color;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Keyboard overlay =====
|
||||
|
||||
class _KeySample {
|
||||
_KeySample({required this.text, required this.timestamp});
|
||||
final String text;
|
||||
final DateTime timestamp;
|
||||
}
|
||||
|
||||
class _KeyboardOverlay extends StatelessWidget {
|
||||
const _KeyboardOverlay({
|
||||
super.key,
|
||||
required this.items,
|
||||
required this.duration,
|
||||
required this.badgeColor,
|
||||
required this.textStyle,
|
||||
});
|
||||
|
||||
final List<_KeySample> items;
|
||||
final Duration duration;
|
||||
final Color badgeColor;
|
||||
final TextStyle textStyle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final now = DateTime.now();
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
for (final item in items)
|
||||
_KeyboardToast(
|
||||
text: item.text,
|
||||
age: now.difference(item.timestamp),
|
||||
duration: duration,
|
||||
badgeColor: badgeColor,
|
||||
textStyle: textStyle,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _KeyboardToast extends StatelessWidget {
|
||||
const _KeyboardToast({
|
||||
required this.text,
|
||||
required this.age,
|
||||
required this.duration,
|
||||
required this.badgeColor,
|
||||
required this.textStyle,
|
||||
});
|
||||
|
||||
final String text;
|
||||
final Duration age;
|
||||
final Duration duration;
|
||||
final Color badgeColor;
|
||||
final TextStyle textStyle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = (age.inMilliseconds / duration.inMilliseconds.clamp(1, 1 << 30)).clamp(0.0, 1.0);
|
||||
final fade = 1.0 - t;
|
||||
|
||||
return Material(
|
||||
child: Opacity(
|
||||
opacity: fade,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 6),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(color: badgeColor, borderRadius: BorderRadius.circular(12)),
|
||||
child: Text(text, style: textStyle),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -27,11 +27,12 @@ class _AppTitleState extends State<AppTitle> {
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body);
|
||||
final tagName = data['tag_name'] as String;
|
||||
final prerelase = data['prerelease'] as bool;
|
||||
final latestVersion = tagName.split('+').first;
|
||||
final currentVersion = 'v${_packageInfoValue!.version}';
|
||||
|
||||
// +1337 releases are considered beta
|
||||
if (latestVersion != currentVersion && !tagName.endsWith("+1337")) {
|
||||
if (latestVersion != currentVersion && !prerelase) {
|
||||
final assets = data['assets'] as List;
|
||||
if (Platform.isAndroid) {
|
||||
final apkUrl = assets.firstOrNullWhere((asset) => asset['name'].endsWith('.apk'))['browser_download_url'];
|
||||
@@ -66,7 +67,7 @@ class _AppTitleState extends State<AppTitle> {
|
||||
}
|
||||
|
||||
void _checkForUpdate() async {
|
||||
if (Platform.isAndroid) {
|
||||
if (!kIsWeb && Platform.isAndroid) {
|
||||
try {
|
||||
final appUpdateInfo = await InAppUpdate.checkForUpdate();
|
||||
if (context.mounted && appUpdateInfo.updateAvailability == UpdateAvailability.updateAvailable) {
|
||||
@@ -91,7 +92,7 @@ class _AppTitleState extends State<AppTitle> {
|
||||
}
|
||||
if (_latestVersionUrlValue == null && !kIsWeb) {
|
||||
final url = await _getLatestVersionUrlIfNewer();
|
||||
if (url != null && mounted) {
|
||||
if (url != null && mounted && !kDebugMode) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('New version available: ${url.split("/").takeLast(2).first.split('%').first}'),
|
||||
@@ -116,7 +117,7 @@ class _AppTitleState extends State<AppTitle> {
|
||||
Text('SwiftControl'),
|
||||
if (_packageInfoValue != null)
|
||||
Text(
|
||||
'v${_packageInfoValue!.version}+${_packageInfoValue!.buildNumber}',
|
||||
'v${_packageInfoValue!.version}',
|
||||
style: TextStyle(fontFamily: "monospace", fontFamilyFallback: <String>["Courier"], fontSize: 12),
|
||||
)
|
||||
else
|
||||
|
||||
@@ -20,7 +20,7 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- window_manager (0.2.0):
|
||||
- window_manager (0.5.0):
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
@@ -71,7 +71,7 @@ SPEC CHECKSUMS:
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
|
||||
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
|
||||
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
|
||||
window_manager: e25faf20d88283a0d46e7b1a759d07261ca27575
|
||||
|
||||
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
||||
|
||||
|
||||
162
pubspec.lock
162
pubspec.lock
@@ -12,10 +12,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: "7dcbd0f87fe5f61cb28da39a1a8b70dbc106e2fe0516f7836eb7bb2948481a12"
|
||||
sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.5"
|
||||
version: "4.0.7"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -60,10 +60,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
|
||||
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
version: "2.0.4"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -132,18 +132,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: "306b78788d1bb569edb7c55d622953c2414ca12445b41c9117963e03afc5c513"
|
||||
sha256: "49413c8ca514dea7633e8def233b25efdf83ec8522955cc2c0e3ad802927e7c6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.3.3"
|
||||
version: "12.1.0"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: device_info_plus_platform_interface
|
||||
sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2"
|
||||
sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.2"
|
||||
version: "7.0.3"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -180,10 +180,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_macos
|
||||
sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc"
|
||||
sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.4+2"
|
||||
version: "0.9.4+4"
|
||||
file_selector_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -233,26 +233,26 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_launcher_icons
|
||||
sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c
|
||||
sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.14.3"
|
||||
version: "0.14.4"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
version: "6.0.0"
|
||||
flutter_local_notifications:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_local_notifications
|
||||
sha256: a9966c850de5e445331b854fa42df96a8020066d67f125a5964cbc6556643f68
|
||||
sha256: "7ed76be64e8a7d01dfdf250b8434618e2a028c9dfa2a3c41dc9b531d4b3fc8a5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "19.4.1"
|
||||
version: "19.4.2"
|
||||
flutter_local_notifications_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -273,18 +273,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_windows
|
||||
sha256: ed46d7ae4ec9d19e4c8fa2badac5fe27ba87a3fe387343ce726f927af074ec98
|
||||
sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
version: "1.0.3"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "5a1e6fb2c0561958d7e4c33574674bda7b77caaca7a33b758876956f2902eea3"
|
||||
sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.27"
|
||||
version: "2.0.30"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -307,10 +307,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
|
||||
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.5.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -331,66 +331,66 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker
|
||||
sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
|
||||
sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
version: "1.2.0"
|
||||
image_picker_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: "8bd392ba8b0c8957a157ae0dc9fcf48c58e6c20908d5880aea1d79734df090e9"
|
||||
sha256: "8dfe08ea7fcf7467dbaf6889e72eebd5e0d6711caae201fdac780eb45232cd02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.12+22"
|
||||
version: "0.8.13+3"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_for_web
|
||||
sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83"
|
||||
sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
version: "3.1.0"
|
||||
image_picker_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_ios
|
||||
sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100"
|
||||
sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.12+2"
|
||||
version: "0.8.13"
|
||||
image_picker_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_linux
|
||||
sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9"
|
||||
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+2"
|
||||
version: "0.2.2"
|
||||
image_picker_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_macos
|
||||
sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1"
|
||||
sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+2"
|
||||
version: "0.2.2"
|
||||
image_picker_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_platform_interface
|
||||
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
|
||||
sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.10.1"
|
||||
version: "2.11.0"
|
||||
image_picker_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_windows
|
||||
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
|
||||
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1+1"
|
||||
version: "0.2.2"
|
||||
in_app_update:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -447,10 +447,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0"
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.1"
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -471,10 +471,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
||||
sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
version: "6.0.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -519,18 +519,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191"
|
||||
sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.3.0"
|
||||
version: "9.0.0"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c"
|
||||
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
version: "3.2.1"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -567,26 +567,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849"
|
||||
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.4.0"
|
||||
version: "12.0.1"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc
|
||||
sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.1.0"
|
||||
version: "13.0.1"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: f84a188e79a35c687c132a0a0556c254747a08561e99ab933f12f6ca71ef3c98
|
||||
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.4.6"
|
||||
version: "9.4.7"
|
||||
permission_handler_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -615,10 +615,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
|
||||
sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
version: "7.0.1"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -647,18 +647,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: posix
|
||||
sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a
|
||||
sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.1"
|
||||
version: "6.0.3"
|
||||
protobuf:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: protobuf
|
||||
sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
|
||||
sha256: de9c9eb2c33f8e933a42932fe1dc504800ca45ebc3d673e6ed7f39754ee4053e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "4.2.0"
|
||||
screen_retriever:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -711,10 +711,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad"
|
||||
sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.8"
|
||||
version: "2.4.13"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -820,10 +820,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timezone
|
||||
sha256: ffc9d5f4d1193534ef051f9254063fa53d588609418c84299956c3db9383587d
|
||||
sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.0"
|
||||
version: "0.10.1"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -852,26 +852,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
|
||||
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.1"
|
||||
version: "6.3.2"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4"
|
||||
sha256: "199bc33e746088546a39cc5f36bac5a278c5e53b40cb3196f99e7345fdcfae6b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.15"
|
||||
version: "6.3.22"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626"
|
||||
sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.2"
|
||||
version: "6.3.4"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -884,10 +884,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
|
||||
sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
version: "3.2.3"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -900,10 +900,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9"
|
||||
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
version: "2.4.1"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -924,10 +924,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.0"
|
||||
version: "15.0.2"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -940,10 +940,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f
|
||||
sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.12.0"
|
||||
version: "5.14.0"
|
||||
win32_registry:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -956,10 +956,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: window_manager
|
||||
sha256: "732896e1416297c63c9e3fb95aea72d0355f61390263982a47fd519169dc5059"
|
||||
sha256: "7eb6d6c4164ec08e1bf978d6e733f3cebe792e2a23fb07cbca25c2872bfdbdcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.3"
|
||||
version: "0.5.1"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -972,10 +972,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
version: "6.6.1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -985,5 +985,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.8.0-0 <4.0.0"
|
||||
dart: ">=3.9.0 <4.0.0"
|
||||
flutter: ">=3.35.0"
|
||||
|
||||
16
pubspec.yaml
16
pubspec.yaml
@@ -1,7 +1,7 @@
|
||||
name: swift_control
|
||||
description: "SwiftControl - Control your virtual riding"
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
version: 2.5.0+5
|
||||
version: 2.6.1+7
|
||||
|
||||
environment:
|
||||
sdk: ^3.7.0
|
||||
@@ -14,18 +14,18 @@ dependencies:
|
||||
flutter_local_notifications: ^19.4.1
|
||||
universal_ble: ^0.21.1
|
||||
intl: any
|
||||
protobuf: ^3.1.0
|
||||
permission_handler: ^11.4.0
|
||||
protobuf: ^4.2.0
|
||||
permission_handler: ^12.0.1
|
||||
dartx: any
|
||||
image_picker: ^1.1.2
|
||||
pointycastle: any
|
||||
window_manager: ^0.4.3
|
||||
device_info_plus: ^11.3.3
|
||||
window_manager: ^0.5.1
|
||||
device_info_plus: ^12.1.0
|
||||
keypress_simulator:
|
||||
path: keypress_simulator/packages/keypress_simulator
|
||||
shared_preferences: ^2.5.3
|
||||
flex_color_scheme: ^8.3.0
|
||||
package_info_plus: ^8.3.0
|
||||
package_info_plus: ^9.0.0
|
||||
in_app_update: ^4.2.5
|
||||
accessibility:
|
||||
path: accessibility
|
||||
@@ -36,7 +36,9 @@ dev_dependencies:
|
||||
sdk: flutter
|
||||
|
||||
flutter_launcher_icons: "^0.14.3"
|
||||
flutter_lints: ^5.0.0
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- CHANGELOG.md
|
||||
|
||||
16
scripts/get_latest_changelog.sh
Executable file
16
scripts/get_latest_changelog.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# Script to extract the latest changelog entry for Play Store uploads
|
||||
# Usage: ./scripts/get_latest_changelog.sh
|
||||
|
||||
# Get the directory where this script is located
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
CHANGELOG_FILE="$PROJECT_ROOT/CHANGELOG.md"
|
||||
|
||||
if [ ! -f "$CHANGELOG_FILE" ]; then
|
||||
echo "Error: CHANGELOG.md not found at $CHANGELOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract the first changelog entry (between first ### and second ###)
|
||||
awk '/^### / {if (count++) exit} count' "$CHANGELOG_FILE" | tail -n +2 | sed 's/^- /• /'
|
||||
69
test/changelog_test.dart
Normal file
69
test/changelog_test.dart
Normal file
@@ -0,0 +1,69 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:swift_control/utils/changelog.dart';
|
||||
|
||||
void main() {
|
||||
group('ChangelogParser', () {
|
||||
test('parses changelog entries correctly', () {
|
||||
const testContent = '''
|
||||
### 2.6.0 (2025-09-28)
|
||||
- Fix crashes on some Android devices
|
||||
- refactor touch placements: show touches on screen
|
||||
- show firmware version of connected device
|
||||
|
||||
### 2.5.0 (2025-09-25)
|
||||
- Improve usability
|
||||
- SwiftControl is now available via the Play Store
|
||||
- SwiftControl will continue to be available to download for free on GitHub
|
||||
''';
|
||||
|
||||
final entries = ChangelogParser.parseContent(testContent);
|
||||
|
||||
expect(entries.length, 2);
|
||||
|
||||
expect(entries[0].version, '2.6.0');
|
||||
expect(entries[0].date, '2025-09-28');
|
||||
expect(entries[0].changes.length, 3);
|
||||
expect(entries[0].changes[0], 'Fix crashes on some Android devices');
|
||||
|
||||
expect(entries[1].version, '2.5.0');
|
||||
expect(entries[1].date, '2025-09-25');
|
||||
expect(entries[1].changes.length, 3);
|
||||
expect(entries[1].changes[0], 'Improve usability');
|
||||
expect(entries[1].changes[1], 'SwiftControl is now available via the Play Store');
|
||||
expect(entries[1].changes[2], 'SwiftControl will continue to be available to download for free on GitHub');
|
||||
});
|
||||
|
||||
test('handles empty content', () {
|
||||
const testContent = '';
|
||||
final entries = ChangelogParser.parseContent(testContent);
|
||||
expect(entries.length, 0);
|
||||
});
|
||||
|
||||
test('handles single entry', () {
|
||||
const testContent = '''
|
||||
### 1.0.0 (2025-01-01)
|
||||
- Initial release
|
||||
''';
|
||||
|
||||
final entries = ChangelogParser.parseContent(testContent);
|
||||
|
||||
expect(entries.length, 1);
|
||||
expect(entries[0].version, '1.0.0');
|
||||
expect(entries[0].changes.length, 1);
|
||||
expect(entries[0].changes[0], 'Initial release');
|
||||
});
|
||||
|
||||
test('ChangelogEntry toString formats correctly', () {
|
||||
final entry = ChangelogEntry(
|
||||
version: '1.0.0',
|
||||
date: '2025-01-01',
|
||||
changes: ['Change 1', 'Change 2'],
|
||||
);
|
||||
|
||||
final result = entry.toString();
|
||||
expect(result, contains('### 1.0.0 (2025-01-01)'));
|
||||
expect(result, contains('- Change 1'));
|
||||
expect(result, contains('- Change 2'));
|
||||
});
|
||||
});
|
||||
}
|
||||
58
test/orientation_test.dart
Normal file
58
test/orientation_test.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:swift_control/pages/touch_area.dart';
|
||||
|
||||
void main() {
|
||||
group('TouchAreaSetupPage Orientation Tests', () {
|
||||
testWidgets('TouchAreaSetupPage should force landscape orientation on init', (WidgetTester tester) async {
|
||||
// Track system chrome method calls
|
||||
final List<MethodCall> systemChromeCalls = [];
|
||||
|
||||
// Mock SystemChrome.setPreferredOrientations
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
|
||||
systemChromeCalls.add(methodCall);
|
||||
return null;
|
||||
});
|
||||
|
||||
// Build the TouchAreaSetupPage
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: TouchAreaSetupPage(),
|
||||
),
|
||||
);
|
||||
|
||||
// Verify that setPreferredOrientations was called with landscape orientations
|
||||
final orientationCalls = systemChromeCalls
|
||||
.where((call) => call.method == 'SystemChrome.setPreferredOrientations')
|
||||
.toList();
|
||||
|
||||
expect(orientationCalls, isNotEmpty);
|
||||
|
||||
// Check if landscape orientations were set
|
||||
final lastOrientationCall = orientationCalls.last;
|
||||
final orientations = lastOrientationCall.arguments as List<String>;
|
||||
|
||||
expect(orientations, contains('DeviceOrientation.landscapeLeft'));
|
||||
expect(orientations, contains('DeviceOrientation.landscapeRight'));
|
||||
expect(orientations, hasLength(2)); // Only landscape orientations
|
||||
});
|
||||
|
||||
test('DeviceOrientation enum values are accessible', () {
|
||||
// Test that we can access the DeviceOrientation enum values
|
||||
final orientations = [
|
||||
DeviceOrientation.landscapeLeft,
|
||||
DeviceOrientation.landscapeRight,
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown,
|
||||
];
|
||||
|
||||
expect(orientations, hasLength(4));
|
||||
expect(orientations, contains(DeviceOrientation.landscapeLeft));
|
||||
expect(orientations, contains(DeviceOrientation.landscapeRight));
|
||||
expect(orientations, contains(DeviceOrientation.portraitUp));
|
||||
expect(orientations, contains(DeviceOrientation.portraitDown));
|
||||
});
|
||||
});
|
||||
}
|
||||
56
test/vibration_setting_test.dart
Normal file
56
test/vibration_setting_test.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:swift_control/utils/settings/settings.dart';
|
||||
|
||||
void main() {
|
||||
group('Vibration Setting Tests', () {
|
||||
late Settings settings;
|
||||
|
||||
setUp(() async {
|
||||
// Initialize SharedPreferences with in-memory storage for testing
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
settings = Settings();
|
||||
await settings.init();
|
||||
});
|
||||
|
||||
test('Vibration setting should default to true', () {
|
||||
expect(settings.getVibrationEnabled(), true);
|
||||
});
|
||||
|
||||
test('Vibration setting should persist when set to false', () async {
|
||||
await settings.setVibrationEnabled(false);
|
||||
expect(settings.getVibrationEnabled(), false);
|
||||
});
|
||||
|
||||
test('Vibration setting should persist when set to true', () async {
|
||||
await settings.setVibrationEnabled(true);
|
||||
expect(settings.getVibrationEnabled(), true);
|
||||
});
|
||||
|
||||
test('Vibration setting should toggle correctly', () async {
|
||||
// Start with default (true)
|
||||
expect(settings.getVibrationEnabled(), true);
|
||||
|
||||
// Toggle to false
|
||||
await settings.setVibrationEnabled(false);
|
||||
expect(settings.getVibrationEnabled(), false);
|
||||
|
||||
// Toggle back to true
|
||||
await settings.setVibrationEnabled(true);
|
||||
expect(settings.getVibrationEnabled(), true);
|
||||
});
|
||||
|
||||
test('Vibration setting should persist across Settings instances', () async {
|
||||
// Set vibration to false
|
||||
await settings.setVibrationEnabled(false);
|
||||
expect(settings.getVibrationEnabled(), false);
|
||||
|
||||
// Create a new Settings instance
|
||||
final newSettings = Settings();
|
||||
await newSettings.init();
|
||||
|
||||
// Should still be false
|
||||
expect(newSettings.getVibrationEnabled(), false);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user