Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fc16e9fb7 | ||
|
|
4329afba1c | ||
|
|
01f87beef5 | ||
|
|
45fecfb4f6 | ||
|
|
3b1c05aba4 | ||
|
|
90a111944a | ||
|
|
66b7e74f84 | ||
|
|
29ef0dfaf4 | ||
|
|
90144948f4 | ||
|
|
bda384953e | ||
|
|
b0fd2a8413 | ||
|
|
2403971063 | ||
|
|
22a0379202 | ||
|
|
c06a426490 | ||
|
|
908e144e1b | ||
|
|
0189019e54 | ||
|
|
5995835d03 | ||
|
|
16e637b256 | ||
|
|
ac2522e860 | ||
|
|
fdb3ad0efc | ||
|
|
f7a01f3c32 | ||
|
|
94fd2c7eff | ||
|
|
f917dfbbb2 | ||
|
|
40bfad6810 | ||
|
|
fefde66b7b | ||
|
|
6869adcc09 | ||
|
|
f5abaec551 | ||
|
|
52fbf693b5 | ||
|
|
bf3995496e | ||
|
|
f7470a032a | ||
|
|
64c9fe5f03 |
74
.github/workflows/build.yml
vendored
@@ -6,23 +6,33 @@ on:
|
||||
build_mac:
|
||||
description: 'Build for macOS'
|
||||
required: false
|
||||
default: 'true'
|
||||
default: true
|
||||
type: boolean
|
||||
build_github:
|
||||
description: 'Build for GitHub'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
build_windows:
|
||||
description: 'Build for Windows'
|
||||
required: false
|
||||
default: 'true'
|
||||
default: true
|
||||
type: boolean
|
||||
build_android:
|
||||
description: 'Build for Android'
|
||||
required: false
|
||||
default: 'true'
|
||||
default: true
|
||||
type: boolean
|
||||
build_ios:
|
||||
description: 'Build for iOS'
|
||||
required: false
|
||||
default: 'true'
|
||||
default: true
|
||||
type: boolean
|
||||
build_web:
|
||||
description: 'Build for Web'
|
||||
required: false
|
||||
default: 'true'
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }}
|
||||
@@ -44,7 +54,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install certificates
|
||||
if: github.event.inputs.build_mac == 'true' || github.event.inputs.build_ios == 'true'
|
||||
if: inputs.build_mac || inputs.build_ios
|
||||
env:
|
||||
DEVELOPER_ID_APPLICATION_P12_BASE64_MAC: ${{ secrets.DEVELOPER_ID_APPLICATION_P12_BASE64_MAC }}
|
||||
DEVELOPER_ID_INSTALLER_P12_BASE64_MAC: ${{ secrets.DEVELOPER_ID_INSTALLER_P12_BASE64_MAC }}
|
||||
@@ -92,20 +102,20 @@ jobs:
|
||||
cache: true
|
||||
|
||||
- name: 🚀 Shorebird Release macOS
|
||||
if: github.event.inputs.build_mac == 'true'
|
||||
if: inputs.build_mac
|
||||
uses: shorebirdtech/shorebird-release@v1
|
||||
with:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
platform: macos
|
||||
|
||||
- name: Decode Keystore
|
||||
if: github.event.inputs.build_android == 'true'
|
||||
if: inputs.build_android
|
||||
run: |
|
||||
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > android/android.keystore;
|
||||
echo "${{ secrets.KEYSTORE_PROPERTIES }}" > android/keystore.properties;
|
||||
|
||||
- name: 🚀 Shorebird Release Android
|
||||
if: github.event.inputs.build_android == 'true'
|
||||
if: inputs.build_android
|
||||
uses: shorebirdtech/shorebird-release@v1
|
||||
with:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
@@ -113,25 +123,25 @@ jobs:
|
||||
args: "--artifact=apk"
|
||||
|
||||
- name: Set Up Flutter
|
||||
if: github.event.inputs.build_web == 'true'
|
||||
if: inputs.build_web
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
|
||||
- name: Build Web
|
||||
if: github.event.inputs.build_web == 'true'
|
||||
if: inputs.build_web
|
||||
run: flutter build web --release --base-href "/swiftcontrol/"
|
||||
|
||||
- name: Upload static files as artifact
|
||||
if: github.event.inputs.build_web == 'true'
|
||||
if: inputs.build_web
|
||||
id: deployment
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: build/web
|
||||
|
||||
- name: Web Deploy
|
||||
if: github.event.inputs.build_web == 'true'
|
||||
if: inputs.build_web
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
- name: Extract latest changelog
|
||||
@@ -142,7 +152,7 @@ jobs:
|
||||
./scripts/get_latest_changelog.sh | head -c 500 > whatsnew/whatsnew-en-US
|
||||
|
||||
- name: 🚀 Shorebird Release iOS
|
||||
if: github.event.inputs.build_ios == 'true'
|
||||
if: inputs.build_ios
|
||||
uses: shorebirdtech/shorebird-release@v1
|
||||
with:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
@@ -150,7 +160,7 @@ jobs:
|
||||
args: "--export-options-plist ios/ExportOptions.plist"
|
||||
|
||||
- name: Prepare App Store authentication key
|
||||
if: github.event.inputs.build_ios == 'true' || github.event.inputs.build_mac == 'true'
|
||||
if: inputs.build_ios || inputs.build_mac
|
||||
env:
|
||||
API_KEY_BASE64: ${{ secrets.APPSTORE_API_KEY_FILE_BASE64 }}
|
||||
APPSTORE_API_KEY: ${{ secrets.APPSTORE_API_KEY }}
|
||||
@@ -159,7 +169,7 @@ jobs:
|
||||
printf %s "$API_KEY_BASE64" | base64 -D > "./private_keys/AuthKey_${APPSTORE_API_KEY}.p8";
|
||||
|
||||
- name: Upload to Play Store
|
||||
if: github.event.inputs.build_android == 'true'
|
||||
if: inputs.build_android
|
||||
uses: r0adkll/upload-google-play@v1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
@@ -169,7 +179,7 @@ jobs:
|
||||
whatsNewDirectory: whatsnew
|
||||
|
||||
- name: Upload to macOS App Store
|
||||
if: github.event.inputs.build_mac == 'true'
|
||||
if: inputs.build_mac
|
||||
env:
|
||||
APPSTORE_API_KEY: ${{ secrets.APPSTORE_API_KEY }}
|
||||
APPSTORE_API_ISSUER_ID: ${{ secrets.APPSTORE_API_ISSUER_ID }}
|
||||
@@ -178,7 +188,7 @@ jobs:
|
||||
xcrun altool --upload-app -f SwiftControl.pkg -t osx --apiKey "$APPSTORE_API_KEY" --apiIssuer "$APPSTORE_API_ISSUER_ID";
|
||||
|
||||
- name: Upload to iOS App Store
|
||||
if: github.event.inputs.build_ios == 'true'
|
||||
if: inputs.build_ios
|
||||
env:
|
||||
APPSTORE_API_KEY: ${{ secrets.APPSTORE_API_KEY }}
|
||||
APPSTORE_API_ISSUER_ID: ${{ secrets.APPSTORE_API_ISSUER_ID }}
|
||||
@@ -186,41 +196,44 @@ jobs:
|
||||
xcrun altool --upload-app -f build/ios/ipa/swift_play.ipa -t ios --apiKey "$APPSTORE_API_KEY" --apiIssuer "$APPSTORE_API_ISSUER_ID";
|
||||
|
||||
- name: Handle Android archives
|
||||
if: github.event.inputs.build_android == 'true'
|
||||
if: inputs.build_android && inputs.build_github
|
||||
run: |
|
||||
cp build/app/outputs/flutter-apk/app-release.apk build/app/outputs/flutter-apk/SwiftControl.android.apk
|
||||
|
||||
- name: Code Signing of macOS app
|
||||
if: github.event.inputs.build_mac == 'true'
|
||||
if: inputs.build_mac && inputs.build_github
|
||||
run: /usr/bin/codesign --deep --force -s "$DEVELOPER_ID_APPLICATION_SIGNING_IDENTITY" --entitlements ../../../../../macos/Runner/Release.entitlements --options runtime SwiftControl.app -v
|
||||
working-directory: build/macos/Build/Products/Release
|
||||
env:
|
||||
DEVELOPER_ID_APPLICATION_SIGNING_IDENTITY: ${{ secrets.DEVELOPER_ID_APPLICATION_SIGNING_IDENTITY }}
|
||||
|
||||
- name: Handle macOS archives
|
||||
if: github.event.inputs.build_mac == 'true'
|
||||
if: inputs.build_mac && inputs.build_github
|
||||
run: |
|
||||
cd build/macos/Build/Products/Release/
|
||||
zip -r SwiftControl.macos.zip SwiftControl.app/
|
||||
|
||||
- name: Upload Android Artifacts
|
||||
if: github.event.inputs.build_android == 'true'
|
||||
if: inputs.build_android && inputs.build_github
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
overwrite: true
|
||||
name: Releases
|
||||
path: |
|
||||
build/app/outputs/flutter-apk/SwiftControl.android.apk
|
||||
|
||||
- name: Upload macOS Artifacts
|
||||
if: github.event.inputs.build_mac == 'true'
|
||||
if: inputs.build_mac && inputs.build_github
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
overwrite: true
|
||||
name: Releases
|
||||
path: |
|
||||
build/macos/Build/Products/Release/SwiftControl.macos.zip
|
||||
|
||||
#10 Extract Version
|
||||
- name: Extract version from pubspec.yaml
|
||||
if: inputs.build_github
|
||||
id: extract_version
|
||||
run: |
|
||||
version=$(grep '^version: ' pubspec.yaml | cut -d ' ' -f 2 | tr -d '\r')
|
||||
@@ -228,6 +241,7 @@ jobs:
|
||||
|
||||
#11 Check if Tag Exists
|
||||
- name: Check if Tag Exists
|
||||
if: inputs.build_github
|
||||
id: check_tag
|
||||
run: |
|
||||
if git rev-parse "v${{ env.VERSION }}" >/dev/null 2>&1; then
|
||||
@@ -236,28 +250,21 @@ jobs:
|
||||
echo "TAG_EXISTS=false" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
#12 Modify Tag if it Exists
|
||||
- name: Modify Tag
|
||||
if: env.TAG_EXISTS == 'true'
|
||||
id: modify_tag
|
||||
run: |
|
||||
new_version="${{ env.VERSION }}-build-${{ github.run_number }}"
|
||||
echo "VERSION=$new_version" >> $GITHUB_ENV
|
||||
|
||||
#13 Create Release
|
||||
- name: Create Release
|
||||
if: inputs.build_github
|
||||
uses: ncipollo/release-action@v1
|
||||
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') }}
|
||||
prerelease: true
|
||||
bodyFile: scripts/RELEASE_NOTES.md
|
||||
tag: v${{ env.VERSION }}
|
||||
token: ${{ secrets.TOKEN }}
|
||||
|
||||
windows:
|
||||
needs: build
|
||||
if: github.event.inputs.build_windows == 'true'
|
||||
if: inputs.build_windows && inputs.build_github
|
||||
name: Build & Release on Windows
|
||||
runs-on: windows-latest
|
||||
|
||||
@@ -334,5 +341,6 @@ jobs:
|
||||
allowUpdates: true
|
||||
artifacts: "build/windows/x64/runner/Release/SwiftControl.windows.zip"
|
||||
bodyFile: scripts/RELEASE_NOTES.md
|
||||
prerelease: true
|
||||
tag: v${{ env.VERSION }}
|
||||
token: ${{ secrets.TOKEN }}
|
||||
|
||||
14
.github/workflows/patch.yml
vendored
@@ -23,6 +23,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: 🐦 Setup Shorebird
|
||||
if: false
|
||||
uses: shorebirdtech/setup-shorebird@v1
|
||||
with:
|
||||
cache: true
|
||||
@@ -70,11 +71,13 @@ jobs:
|
||||
cp $PP_PATH_MACOS ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
|
||||
- name: Decode Keystore
|
||||
if: false
|
||||
run: |
|
||||
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > android/android.keystore;
|
||||
echo "${{ secrets.KEYSTORE_PROPERTIES }}" > android/keystore.properties;
|
||||
|
||||
- name: 🚀 Shorebird Patch macOS
|
||||
if: false
|
||||
uses: shorebirdtech/shorebird-patch@v1
|
||||
with:
|
||||
platform: macos
|
||||
@@ -82,6 +85,7 @@ jobs:
|
||||
args: '--allow-asset-diffs'
|
||||
|
||||
- name: 🚀 Shorebird Patch Android
|
||||
if: false
|
||||
uses: shorebirdtech/shorebird-patch@v1
|
||||
with:
|
||||
platform: android
|
||||
@@ -89,6 +93,7 @@ jobs:
|
||||
args: '--allow-asset-diffs'
|
||||
|
||||
- name: 🚀 Shorebird Patch iOS
|
||||
if: false
|
||||
uses: shorebirdtech/shorebird-patch@v1
|
||||
with:
|
||||
platform: ios
|
||||
@@ -103,15 +108,17 @@ jobs:
|
||||
|
||||
# shorebird struggles with the app from GitHub
|
||||
- name: Build macOS
|
||||
run: flutter build macos --release;
|
||||
|
||||
- name: Sign macOS build
|
||||
env:
|
||||
DEVELOPER_ID_APPLICATION_SIGNING_IDENTITY: ${{ secrets.DEVELOPER_ID_APPLICATION_SIGNING_IDENTITY }}
|
||||
run: |
|
||||
flutter build macos --release;
|
||||
cd build/macos/Build/Products/Release/;
|
||||
zip -r SwiftControl.macos.zip SwiftControl.app/;
|
||||
version=$(grep '^version: ' pubspec.yaml | cut -d ' ' -f 2 | tr -d '\r');
|
||||
echo "VERSION=$version" >> $GITHUB_ENV;
|
||||
cd build/macos/Build/Products/Release/;
|
||||
/usr/bin/codesign --deep --force -s "$DEVELOPER_ID_APPLICATION_SIGNING_IDENTITY" --entitlements ../../../../../macos/Runner/Release.entitlements --options runtime SwiftControl.app -v;
|
||||
zip -r SwiftControl.macos.zip SwiftControl.app/;
|
||||
|
||||
#9 Upload Artifacts
|
||||
- name: Upload Artifacts
|
||||
@@ -134,6 +141,7 @@ jobs:
|
||||
|
||||
windows:
|
||||
name: Patch Windows
|
||||
if: false
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
|
||||
2
.github/workflows/web.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: "Build"
|
||||
name: "Build Web"
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
### 3.0.4 (not released yet)
|
||||
### 3.1.0 (2025-10-17)
|
||||
- new app icon
|
||||
- adjusted MyWhoosh keyboard navigation mapping (thanks @bin101)
|
||||
- initial support for Wahook Kickr Bike Shift (thanks @MattW2)
|
||||
- support for Wahook Kickr Bike Shift (thanks @MattW2)
|
||||
- initial support for Elite Square Smart Frame
|
||||
|
||||
### 3.0.3 (2025-10-12)
|
||||
|
||||
11
README.md
@@ -1,6 +1,6 @@
|
||||
# SwiftControl
|
||||
|
||||
<img src="logo.jpg" alt="SwiftControl Logo"/>
|
||||
<img src="logo.png" alt="SwiftControl Logo"/>
|
||||
|
||||
## Description
|
||||
|
||||
@@ -44,13 +44,16 @@ Get the latest version for Windows here: https://github.com/jonasbark/swiftcontr
|
||||
|
||||
## Supported Platforms
|
||||
|
||||
| Platform you want to run your Trainer app, e.g. MyWhoosh on | Possible | Link | Information |
|
||||
Follow this compatibility matrix. It all depends on where you want to run your trainer app (e.g. MyWhoosh on):
|
||||
|
||||
| Run Trainer app (MyWhoosh, ...) on: | Possible | Link | Information |
|
||||
|-------------------------------------------------------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Android | ✅ | <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> | |
|
||||
| iPad (and possibly Apple TV) | ✅ | <a href="https://apps.apple.com/us/app/swiftcontrol/id6753721284?platform=iphone"><img width="270" height="80" alt="App Store" src="https://github.com/user-attachments/assets/c23f977a-48f6-4951-811e-ae530dbfa014" /></a> | You will need to use SwiftControl as a "remote" to control the trainer app on your iPad. Typically you would use an iPhone or an Android phone for that. |
|
||||
| iPad | ✅ | <a href="https://apps.apple.com/us/app/swiftcontrol/id6753721284?platform=iphone"><img width="270" height="80" alt="App Store" src="https://github.com/user-attachments/assets/c23f977a-48f6-4951-811e-ae530dbfa014" /></a> | You will need to use SwiftControl as a "remote" to control the trainer app on your iPad. Typically you would use an iPhone or an Android phone for that. |
|
||||
| Windows | ✅ | [Get it here](https://github.com/jonasbark/swiftcontrol/releases) | - Windows may flag the app as virus. It likely does so because the app controls the mouse and keyboard.<br>- Bluetooth connection unstable? You may need to use an [external Bluetooth adapter](https://github.com/jonasbark/swiftcontrol/issues/14#issuecomment-3193839509).<br>- Make sure your Zwift device is not paired with Windows Bluetooth settings: [more information](https://github.com/jonasbark/swiftcontrol/issues/70). |
|
||||
| macOS | ✅ | <a href="https://apps.apple.com/us/app/swiftcontrol/id6753721284?platform=mac"><img width="270" height="80" alt="Mac App Store" src="https://github.com/user-attachments/assets/b3552436-409c-43b0-ba7d-b6a72ae30ff1" /></a> | |
|
||||
| iPhone | ❌ | | Note that you can't run SwiftControl and your trainer app on the same iPhone due to iOS limitations, but you can use it to remotely control MyWhoosh and similar on e.g. an iPad. |
|
||||
| iPhone | ❌ | <a href="https://apps.apple.com/us/app/swiftcontrol/id6753721284?platform=iphone"><img width="270" height="80" alt="App Store" src="https://github.com/user-attachments/assets/c23f977a-48f6-4951-811e-ae530dbfa014" /></a> | Note that you can't run SwiftControl and your trainer app on the same iPhone due to iOS limitations, but you can use it to remotely control MyWhoosh and similar on e.g. an iPad. |
|
||||
| Apple TV | ❌ | | Apple TV does not support touch inputs. Instead you can use e.g. SwiftControl with MyWhoosh Link to control your session |
|
||||
|
||||
|
||||
For testing purposes you can also run it on [Web](https://jonasbark.github.io/swiftcontrol/) but this is just a tech demo - you won't be able to control other apps.
|
||||
|
||||
@@ -26,3 +26,6 @@ If you don't do that SwiftControl will need to reconnect every minute.
|
||||
iOS seems to be buggy here - try this in the iOS settings:
|
||||
AssistiveTouch settings > Pointer Devices > Devices > Connected Devices > iPhone (or SwiftControl iOS) > Button 1
|
||||
switch the setting to None, then back to Single-Tap and it should work again
|
||||
|
||||
## SwiftControl crashes on Windows when searching for the device
|
||||
You're probably running into [this](https://github.com/jonasbark/swiftcontrol/issues/70) issue. Disconnect your controller device (e.g. Zwift Play) from Windows Bluetooth settings.
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
</item>
|
||||
</layer-list>
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
</item>
|
||||
</layer-list>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.0 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 4.3 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 13 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 125 KiB |
@@ -1,32 +0,0 @@
|
||||
# flutter pub run flutter_launcher_icons
|
||||
flutter_launcher_icons:
|
||||
image_path: "icon.png"
|
||||
|
||||
android: "ic_launcher"
|
||||
# image_path_android: "assets/icon/icon.png"
|
||||
min_sdk_android: 24 # android min sdk min:16, default 21
|
||||
# adaptive_icon_background: "assets/icon/background.png"
|
||||
# adaptive_icon_foreground: "assets/icon/foreground.png"
|
||||
# adaptive_icon_monochrome: "assets/icon/monochrome.png"
|
||||
|
||||
ios: true
|
||||
# image_path_ios: "assets/icon/icon.png"
|
||||
remove_alpha_ios: true
|
||||
# image_path_ios_dark_transparent: "assets/icon/icon_dark.png"
|
||||
# image_path_ios_tinted_grayscale: "assets/icon/icon_tinted.png"
|
||||
# desaturate_tinted_to_grayscale_ios: true
|
||||
|
||||
web:
|
||||
generate: true
|
||||
image_path: "icon.png"
|
||||
background_color: "#ffffff"
|
||||
theme_color: "#ffffff"
|
||||
|
||||
windows:
|
||||
generate: true
|
||||
image_path: "icon.png"
|
||||
icon_size: 48 # min:48, max:256, default: 48
|
||||
|
||||
macos:
|
||||
generate: true
|
||||
image_path: "icon.png"
|
||||
@@ -13,8 +13,6 @@ PODS:
|
||||
- Flutter
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- restart (1.0.0):
|
||||
- Flutter
|
||||
- restart_app (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
@@ -36,7 +34,6 @@ DEPENDENCIES:
|
||||
- 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`)
|
||||
- restart (from `.symlinks/plugins/restart/ios`)
|
||||
- restart_app (from `.symlinks/plugins/restart_app/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- universal_ble (from `.symlinks/plugins/universal_ble/darwin`)
|
||||
@@ -58,8 +55,6 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
restart:
|
||||
:path: ".symlinks/plugins/restart/ios"
|
||||
restart_app:
|
||||
:path: ".symlinks/plugins/restart_app/ios"
|
||||
shared_preferences_foundation:
|
||||
@@ -79,7 +74,6 @@ SPEC CHECKSUMS:
|
||||
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
restart: b5fe16e6e038f0024b2f3af43768e9d2a1557554
|
||||
restart_app: 806659942bf932f6ce51c5372f91ce5e81c8c14a
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
|
||||
|
||||
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 866 B |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 35 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 30 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
@@ -1 +1,134 @@
|
||||
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"filename": "AppIcon@2x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "2x",
|
||||
"size": "60x60"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon@3x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "3x",
|
||||
"size": "60x60"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon~ipad.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x",
|
||||
"size": "76x76"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon@2x~ipad.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x",
|
||||
"size": "76x76"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon-83.5@2x~ipad.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x",
|
||||
"size": "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon-40@2x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "2x",
|
||||
"size": "40x40"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon-40@3x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "3x",
|
||||
"size": "40x40"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon-40~ipad.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x",
|
||||
"size": "40x40"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon-40@2x~ipad.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x",
|
||||
"size": "40x40"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon-20@2x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "2x",
|
||||
"size": "20x20"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon-20@3x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "3x",
|
||||
"size": "20x20"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon-20~ipad.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x",
|
||||
"size": "20x20"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon-20@2x~ipad.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x",
|
||||
"size": "20x20"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon-29.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "1x",
|
||||
"size": "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon-29@2x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "2x",
|
||||
"size": "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon-29@3x.png",
|
||||
"idiom": "iphone",
|
||||
"scale": "3x",
|
||||
"size": "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon-29~ipad.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "1x",
|
||||
"size": "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon-29@2x~ipad.png",
|
||||
"idiom": "ipad",
|
||||
"scale": "2x",
|
||||
"size": "29x29"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon-60@2x~car.png",
|
||||
"idiom": "car",
|
||||
"scale": "2x",
|
||||
"size": "60x60"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon-60@3x~car.png",
|
||||
"idiom": "car",
|
||||
"scale": "3x",
|
||||
"size": "60x60"
|
||||
},
|
||||
{
|
||||
"filename": "AppIcon~ios-marketing.png",
|
||||
"idiom": "ios-marketing",
|
||||
"scale": "1x",
|
||||
"size": "1024x1024"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "iconkitchen",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 880 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 37 KiB |
6
ios/Runner/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/AppIcon-min 1.png
vendored
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/AppIcon-min 2.png
vendored
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/AppIcon-min.png
vendored
Normal file
|
After Width: | Height: | Size: 212 KiB |
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AppIcon-min 2.png",
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-min 1.png",
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon-min.png",
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B |
@@ -1,5 +0,0 @@
|
||||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||
@@ -1,8 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
@@ -14,9 +16,11 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
<rect key="frame" x="111.33333333333333" y="340.66666666666669" width="170.66666666666669" height="170.66666666666669"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@@ -28,10 +32,10 @@
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
<point key="canvasLocation" x="80.916030534351137" y="264.08450704225356"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
<image name="LaunchImage" width="170.66667175292969" height="170.66667175292969"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -67,11 +67,11 @@ public class KeypressSimulatorMacosPlugin: NSObject, FlutterPlugin {
|
||||
let point = CGPoint(x: x, y: y)
|
||||
|
||||
// Move mouse to the point
|
||||
/*let move = CGEvent(mouseEventSource: nil,
|
||||
let move = CGEvent(mouseEventSource: nil,
|
||||
mouseType: .mouseMoved,
|
||||
mouseCursorPosition: point,
|
||||
mouseButton: .left)
|
||||
move?.post(tap: .cghidEventTap)*/
|
||||
move?.post(tap: .cghidEventTap)
|
||||
|
||||
if (keyDown) {
|
||||
// Mouse down
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
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"
|
||||
@@ -7,120 +5,4 @@ class BleUuid {
|
||||
|
||||
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();
|
||||
static final ZWIFT_SYNC_RX_CHARACTERISTIC_UUID = "00000003-19CA-4651-86E5-FA29DCDD09D1".toLowerCase();
|
||||
static final ZWIFT_SYNC_TX_CHARACTERISTIC_UUID = "00000004-19CA-4651-86E5-FA29DCDD09D1".toLowerCase();
|
||||
}
|
||||
|
||||
class Constants {
|
||||
static const ZWIFT_MANUFACTURER_ID = 2378; // Zwift, Inc => 0x094A
|
||||
|
||||
// Zwift Play = RC1
|
||||
static const RC1_LEFT_SIDE = 0x03;
|
||||
static const RC1_RIGHT_SIDE = 0x02;
|
||||
|
||||
// Zwift Ride
|
||||
static const RIDE_RIGHT_SIDE = 0x07;
|
||||
static const RIDE_LEFT_SIDE = 0x08;
|
||||
|
||||
// Zwift Click = BC1
|
||||
static const BC1 = 0x09;
|
||||
|
||||
// Zwift Click v2 Right (unconfirmed)
|
||||
static const CLICK_V2_RIGHT_SIDE = 0x0A;
|
||||
// Zwift Click v2 Right (unconfirmed)
|
||||
static const CLICK_V2_LEFT_SIDE = 0x0B;
|
||||
|
||||
static final RIDE_ON = Uint8List.fromList([0x52, 0x69, 0x64, 0x65, 0x4f, 0x6e]);
|
||||
static final VIBRATE_PATTERN = Uint8List.fromList([0x12, 0x12, 0x08, 0x0A, 0x06, 0x08, 0x02, 0x10, 0x00, 0x18]);
|
||||
|
||||
// these don't actually seem to matter, its just the header has to be 7 bytes RIDEON + 2
|
||||
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;
|
||||
static const int PLAY_NOTIFICATION_MESSAGE_TYPE = 7;
|
||||
static const int RIDE_NOTIFICATION_MESSAGE_TYPE = 35; // 0x23
|
||||
|
||||
// see this if connected to Core then Zwift connects to it. just one byte
|
||||
static const DISCONNECT_MESSAGE_TYPE = 0xFE;
|
||||
}
|
||||
|
||||
enum DeviceType {
|
||||
click,
|
||||
clickV2Right,
|
||||
clickV2Left,
|
||||
playLeft,
|
||||
playRight,
|
||||
rideRight,
|
||||
rideLeft;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return super.toString().split('.').last;
|
||||
}
|
||||
|
||||
// add constructor
|
||||
static DeviceType? fromManufacturerData(int data) {
|
||||
switch (data) {
|
||||
case Constants.BC1:
|
||||
return DeviceType.click;
|
||||
case Constants.CLICK_V2_RIGHT_SIDE:
|
||||
return DeviceType.clickV2Right;
|
||||
case Constants.CLICK_V2_LEFT_SIDE:
|
||||
return DeviceType.clickV2Left;
|
||||
case Constants.RC1_LEFT_SIDE:
|
||||
return DeviceType.playLeft;
|
||||
case Constants.RC1_RIGHT_SIDE:
|
||||
return DeviceType.playRight;
|
||||
case Constants.RIDE_RIGHT_SIDE:
|
||||
return DeviceType.rideRight;
|
||||
case Constants.RIDE_LEFT_SIDE:
|
||||
return DeviceType.rideLeft;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ import 'package:swift_control/utils/actions/android.dart';
|
||||
import 'package:swift_control/utils/requirements/android.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
import '../bluetooth/ble.dart';
|
||||
import 'devices/base_device.dart';
|
||||
import 'devices/zwift/constants.dart';
|
||||
import 'messages/notification.dart';
|
||||
|
||||
class Connection {
|
||||
@@ -44,7 +44,7 @@ class Connection {
|
||||
} else {
|
||||
final manufacturerData = result.manufacturerDataList;
|
||||
final data = manufacturerData
|
||||
.firstOrNullWhere((e) => e.companyId == Constants.ZWIFT_MANUFACTURER_ID)
|
||||
.firstOrNullWhere((e) => e.companyId == ZwiftConstants.ZWIFT_MANUFACTURER_ID)
|
||||
?.payload;
|
||||
_actionStreams.add(LogNotification('Found unknown device with identifier: ${data?.firstOrNull}'));
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'dart:async';
|
||||
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:swift_control/bluetooth/ble.dart';
|
||||
import 'package:swift_control/bluetooth/devices/wahoo/wahoo_kickr_bike_shift.dart';
|
||||
import 'package:swift_control/bluetooth/devices/zwift/constants.dart';
|
||||
import 'package:swift_control/bluetooth/devices/zwift/zwift_click.dart';
|
||||
import 'package:swift_control/bluetooth/devices/zwift/zwift_clickv2.dart';
|
||||
import 'package:swift_control/bluetooth/devices/zwift/zwift_play.dart';
|
||||
@@ -31,8 +31,8 @@ abstract class BaseDevice {
|
||||
Set<ControllerButton> _previouslyPressedButtons = <ControllerButton>{};
|
||||
|
||||
static List<String> servicesToScan = [
|
||||
BleUuid.ZWIFT_CUSTOM_SERVICE_UUID,
|
||||
BleUuid.ZWIFT_RIDE_CUSTOM_SERVICE_UUID,
|
||||
ZwiftConstants.ZWIFT_CUSTOM_SERVICE_UUID,
|
||||
ZwiftConstants.ZWIFT_RIDE_CUSTOM_SERVICE_UUID,
|
||||
SquareConstants.SERVICE_UUID,
|
||||
WahooKickrBikeShiftConstants.SERVICE_UUID,
|
||||
];
|
||||
@@ -65,25 +65,27 @@ abstract class BaseDevice {
|
||||
if (device != null) {
|
||||
return device;
|
||||
} else if (scanResult.services.containsAny([
|
||||
BleUuid.ZWIFT_CUSTOM_SERVICE_UUID,
|
||||
BleUuid.ZWIFT_RIDE_CUSTOM_SERVICE_UUID,
|
||||
ZwiftConstants.ZWIFT_CUSTOM_SERVICE_UUID,
|
||||
ZwiftConstants.ZWIFT_RIDE_CUSTOM_SERVICE_UUID,
|
||||
])) {
|
||||
// otherwise use the manufacturer data to identify the device
|
||||
final manufacturerData = scanResult.manufacturerDataList;
|
||||
final data = manufacturerData.firstOrNullWhere((e) => e.companyId == Constants.ZWIFT_MANUFACTURER_ID)?.payload;
|
||||
final data = manufacturerData
|
||||
.firstOrNullWhere((e) => e.companyId == ZwiftConstants.ZWIFT_MANUFACTURER_ID)
|
||||
?.payload;
|
||||
|
||||
if (data == null || data.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final type = DeviceType.fromManufacturerData(data.first);
|
||||
final type = ZwiftDeviceType.fromManufacturerData(data.first);
|
||||
return switch (type) {
|
||||
DeviceType.click => ZwiftClick(scanResult),
|
||||
DeviceType.playRight => ZwiftPlay(scanResult),
|
||||
DeviceType.playLeft => ZwiftPlay(scanResult),
|
||||
DeviceType.rideLeft => ZwiftRide(scanResult),
|
||||
ZwiftDeviceType.click => ZwiftClick(scanResult),
|
||||
ZwiftDeviceType.playRight => ZwiftPlay(scanResult),
|
||||
ZwiftDeviceType.playLeft => ZwiftPlay(scanResult),
|
||||
ZwiftDeviceType.rideLeft => ZwiftRide(scanResult),
|
||||
//DeviceType.rideRight => ZwiftRide(scanResult), // see comment above
|
||||
DeviceType.clickV2Left => ZwiftClickV2(scanResult),
|
||||
ZwiftDeviceType.clickV2Left => ZwiftClickV2(scanResult),
|
||||
//DeviceType.clickV2Right => ZwiftClickV2(scanResult), // see comment above
|
||||
_ => null,
|
||||
};
|
||||
@@ -156,6 +158,8 @@ abstract class BaseDevice {
|
||||
}
|
||||
_previouslyPressedButtons.clear();
|
||||
} else {
|
||||
actionStreamInternal.add(ButtonNotification(buttonsClicked: buttonsClicked));
|
||||
|
||||
// Handle release events for buttons that are no longer pressed
|
||||
final buttonsReleased = _previouslyPressedButtons.difference(buttonsClicked.toSet()).toList();
|
||||
final wasLongPress =
|
||||
|
||||
@@ -37,6 +37,7 @@ class EliteSquare extends BaseDevice {
|
||||
if (characteristic == SquareConstants.CHARACTERISTIC_UUID) {
|
||||
final fullValue = _bytesToHex(bytes);
|
||||
final currentValue = _extractButtonCode(fullValue);
|
||||
actionStreamInternal.add(LogNotification('Received $fullValue - vs $currentValue (last: $_lastValue)'));
|
||||
|
||||
if (_lastValue != null) {
|
||||
final currentRelevantPart = fullValue.length >= 19
|
||||
@@ -48,9 +49,7 @@ class EliteSquare extends BaseDevice {
|
||||
|
||||
if (currentRelevantPart != lastRelevantPart) {
|
||||
final buttonClicked = SquareConstants.BUTTON_MAPPING[currentValue];
|
||||
if (buttonClicked != null) {
|
||||
actionStreamInternal.add(LogNotification('Button pressed: $buttonClicked'));
|
||||
}
|
||||
actionStreamInternal.add(LogNotification('Button pressed: $buttonClicked'));
|
||||
handleButtonsClicked([
|
||||
if (buttonClicked != null) buttonClicked,
|
||||
]);
|
||||
|
||||
118
lib/bluetooth/devices/zwift/constants.dart
Normal file
@@ -0,0 +1,118 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
class ZwiftConstants {
|
||||
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();
|
||||
static final ZWIFT_SYNC_RX_CHARACTERISTIC_UUID = "00000003-19CA-4651-86E5-FA29DCDD09D1".toLowerCase();
|
||||
static final ZWIFT_SYNC_TX_CHARACTERISTIC_UUID = "00000004-19CA-4651-86E5-FA29DCDD09D1".toLowerCase();
|
||||
|
||||
static const ZWIFT_MANUFACTURER_ID = 2378; // Zwift, Inc => 0x094A
|
||||
|
||||
// Zwift Play = RC1
|
||||
static const RC1_LEFT_SIDE = 0x03;
|
||||
static const RC1_RIGHT_SIDE = 0x02;
|
||||
|
||||
// Zwift Ride
|
||||
static const RIDE_RIGHT_SIDE = 0x07;
|
||||
static const RIDE_LEFT_SIDE = 0x08;
|
||||
|
||||
// Zwift Click = BC1
|
||||
static const BC1 = 0x09;
|
||||
|
||||
// Zwift Click v2 Right (unconfirmed)
|
||||
static const CLICK_V2_RIGHT_SIDE = 0x0A;
|
||||
// Zwift Click v2 Right (unconfirmed)
|
||||
static const CLICK_V2_LEFT_SIDE = 0x0B;
|
||||
|
||||
static final RIDE_ON = Uint8List.fromList([0x52, 0x69, 0x64, 0x65, 0x4f, 0x6e]);
|
||||
static final VIBRATE_PATTERN = Uint8List.fromList([0x12, 0x12, 0x08, 0x0A, 0x06, 0x08, 0x02, 0x10, 0x00, 0x18]);
|
||||
|
||||
// these don't actually seem to matter, its just the header has to be 7 bytes RIDEON + 2
|
||||
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;
|
||||
static const int PLAY_NOTIFICATION_MESSAGE_TYPE = 7;
|
||||
static const int RIDE_NOTIFICATION_MESSAGE_TYPE = 35; // 0x23
|
||||
|
||||
// see this if connected to Core then Zwift connects to it. just one byte
|
||||
static const DISCONNECT_MESSAGE_TYPE = 0xFE;
|
||||
}
|
||||
|
||||
enum ZwiftDeviceType {
|
||||
click,
|
||||
clickV2Right,
|
||||
clickV2Left,
|
||||
playLeft,
|
||||
playRight,
|
||||
rideRight,
|
||||
rideLeft;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return super.toString().split('.').last;
|
||||
}
|
||||
|
||||
// add constructor
|
||||
static ZwiftDeviceType? fromManufacturerData(int data) {
|
||||
switch (data) {
|
||||
case ZwiftConstants.BC1:
|
||||
return ZwiftDeviceType.click;
|
||||
case ZwiftConstants.CLICK_V2_RIGHT_SIDE:
|
||||
return ZwiftDeviceType.clickV2Right;
|
||||
case ZwiftConstants.CLICK_V2_LEFT_SIDE:
|
||||
return ZwiftDeviceType.clickV2Left;
|
||||
case ZwiftConstants.RC1_LEFT_SIDE:
|
||||
return ZwiftDeviceType.playLeft;
|
||||
case ZwiftConstants.RC1_RIGHT_SIDE:
|
||||
return ZwiftDeviceType.playRight;
|
||||
case ZwiftConstants.RIDE_RIGHT_SIDE:
|
||||
return ZwiftDeviceType.rideRight;
|
||||
case ZwiftConstants.RIDE_LEFT_SIDE:
|
||||
return ZwiftDeviceType.rideLeft;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||