Compare commits

..

2 Commits

Author SHA1 Message Date
Roberto Viola
a5565f5913 Update build_ios.yml 2021-07-27 16:09:14 +02:00
Roberto Viola
b4e41131a9 Create build_ios.yml 2021-07-27 16:07:40 +02:00
1717 changed files with 85923 additions and 471029 deletions

View File

@@ -1,11 +0,0 @@
{
"permissions": {
"allow": [
"Bash(tshark:*)",
"Bash(find:*)",
"Bash(python3:*)",
"Bash(git log:*)",
"Bash(git ls-tree:*)"
]
}
}

2
.github/FUNDING.yml vendored
View File

@@ -7,6 +7,6 @@ ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: cagnulein
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://www.buymeacoffee.com/cagnulein'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -32,10 +32,5 @@ If applicable, add screenshots to help explain your problem.
- OS: [e.g. iOS8.1]
- Version [e.g. 22]
**Append a debug log**
Follow this guide https://github.com/cagnulein/qdomyos-zwift/wiki/How-do-i-get-the-debug-log-in-case-something-doesn't-work%3F
**Additional context**
Add any other context about the problem here.

17
.github/stale.yml vendored
View File

@@ -1,17 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 15
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
#closeComment: false

43
.github/workflows/build_ios.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
# This is a basic workflow to help you get started with Actions
name: build-ios
# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the master branch
#push:
# branches: [ master ]
#pull_request:
# branches: [ master ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build: # make sure build/ci work properly
runs-on: macOS-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@v2
- uses: Apple-Actions/import-codesign-certs@v1
with:
p12-file-base64: ${{ secrets.CERTIFICATES_FILE_BASE64 }}
p12-password: ${{ secrets.CERTIFICATES_PASSWORD }}
- uses: Apple-Actions/download-provisioning-profiles@v1
with:
bundle-id: org.cagnulein.qdomyoszwift
issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
- name: "#️⃣ Generate Build Number"
id: buildnumber
uses: einaregilsson/build-number@v2
with:
token: ${{ secrets.github_token }}
- run: ./Build
# - uses: Apple-Actions/upload-testflight-build@master
# with:
# app-path: .build/Artifacts/Example-iOS.ipa/Example-iOS.ipa
# issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
# api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
# api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}

File diff suppressed because it is too large Load Diff

20
.gitignore vendored
View File

@@ -1,5 +1,3 @@
src/qdomyos-zwift.pro.user
.idea/
src/Makefile
@@ -20,14 +18,9 @@ src/build/*
src/debug-*
src/secret.h
*.swo
*.swp
build-qdomyos-zwift-Android_Qt_5_15_2_Clang_Multi_Abi-Debug/*
**/node_modules/*
template-examples/youtube-viewer/node_modules/*
template-examples/youtube-viewer/*.json
template-examples/youtube-viewer/.eslintrc.js
@@ -40,16 +33,3 @@ template-examples/train-program-saver/*.json
template-examples/train-program-saver/.eslintrc.js
template-examples/train-program-saver/.jshintrc
template-examples/train-program-saver/debug.js
google_test/*
# Qt-es
*.pro.user
*build-*
!build-qdomyos-zwift-Qt_*_for_iOS-Debug # Needed for Apple Watch
src/inner_templates/googlemaps/cesium-key.js
*.autosave
.vscode/settings.json
/tst/Devices/.vs
src/inner_templates/googlemaps/cesium-key.js
src/qdomyos-zwift.pro.user.49de507

14
.gitmodules vendored
View File

@@ -3,16 +3,4 @@
url = https://github.com/KDAB/android_openssl.git
[submodule "src/smtpclient"]
path = src/smtpclient
url = https://github.com/cagnulein/SmtpClient-for-Qt.git
branch = cagnulein-patch-2
[submodule "tst/googletest"]
path = tst/googletest
url = https://github.com/google/googletest.git
tag = release-1.12.1
[submodule "src/qthttpserver"]
path = src/qthttpserver
url = https://github.com/qt-labs/qthttpserver
[submodule "zwiftplay"]
path = zwiftplay
url = https://github.com/cagnulein/zwiftplay.git
branch = lib
url = https://github.com/bluetiger9/SmtpClient-for-Qt.git

16
.vscode/launch.json vendored
View File

@@ -1,16 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "(Windows) Launch",
"type": "cppvsdbg",
"request": "launch",
"program": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/qdomyos-zwift.exe",
"symbolSearchPath": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/qdomyos-zwift.pdb",
"sourceFileMap": {
"d:/a/qdomyos-zwift/qdomyos-zwift": "c:/work/qdomyos-zwift/",
"compiled_source_path": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/"
}
}
]
}

473
CLAUDE.md
View File

@@ -1,473 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
QDomyos-Zwift is a Qt-based application that bridges fitness equipment (treadmills, bikes, ellipticals, rowers) with virtual training platforms like Zwift. It acts as a Bluetooth intermediary, connecting physical equipment to fitness apps while providing enhanced features like Peloton integration, power zone training, and workout programs.
## Build System & Commands
### Build Commands
```bash
# Build entire project (use subdirs TEMPLATE)
qmake
make
# Build specific configurations
qmake -r # Recursive build
make debug # Debug build
make release # Release build
# Clean build
make clean
make distclean
```
### Platform-Specific Builds
```bash
# Android
qmake -spec android-clang
make
# iOS
qmake -spec macx-ios-clang
make
# Windows (MinGW)
qmake -spec win32-g++
make
```
### Testing
```bash
# Build and run tests (requires main app built first)
cd tst
qmake
make
./qdomyos-zwift-tests
# Run with XML output for CI
GTEST_OUTPUT=xml:test-results/ GTEST_COLOR=1 ./qdomyos-zwift-tests
```
### No-GUI Mode
```bash
# Run application without GUI
sudo ./qdomyos-zwift -no-gui
```
## Architecture Overview
### Device Architecture
The application follows a hierarchical device architecture:
1. **Base Class**: `bluetoothdevice` - Abstract base for all fitness devices
- Manages Bluetooth connectivity via Qt's QLowEnergyController
- Defines common metrics (speed, cadence, heart rate, power, distance)
- Integrates with virtual devices for app connectivity
2. **Device Type Classes**: Inherit from `bluetoothdevice`
- `bike` - Bike-specific features (resistance, gears, power zones)
- `treadmill` - Treadmill features (speed control, inclination, pace)
- `elliptical` - Combined bike/treadmill features
- `rower` - Rowing metrics (stroke count, 500m pace)
- `stairclimber` - Step counting and climbing metrics
- `jumprope` - Jump sequence tracking
3. **Concrete Implementations**: Inherit from device type classes
- Located in `src/devices/[devicename]/` folders
- Examples: `domyosbike`, `pelotonbike`, `ftmsbike`
### Virtual Device System
- `virtualdevice` - Abstract base for virtual representations
- `virtualbike`, `virtualtreadmill`, etc. - Advertise to external apps
- Enables bidirectional communication between physical and virtual devices
### Bluetooth Management
- `bluetooth` class acts as device factory and connection manager
- `discoveryoptions` configures device discovery process
- Supports multiple connection types (Bluetooth LE, TCP, UDP)
## Key Development Areas
### Adding New Device Support
1. Create device folder in `src/devices/[devicename]/`
2. Implement device class inheriting from appropriate base type
3. Add device detection logic to `bluetooth.cpp`
4. Update `qdomyos-zwift.pri` with new source files
5. Add tests in `tst/Devices/` following existing patterns
### Adding Device Detection to bluetooth.cpp
**CRITICAL: Always verify device pattern conflicts before adding to bluetooth.cpp**
When adding a new device pattern to `src/devices/bluetooth.cpp`, you **MUST** follow these verification steps:
1. **Search for Similar Patterns**: Use grep/search to find all existing device patterns that might conflict
- Search for device name prefixes (e.g., if adding "KS-NG-", search for all "KS-" patterns)
- Check patterns in all device type cases (bikes, treadmills, ellipticals, rowers, etc.)
2. **Analyze Pattern Specificity**: Understand the pattern hierarchy
- More specific patterns should be checked BEFORE less specific ones
- Example: "KS-NGCH-" is more specific than "KS-NG-"
- The order matters: devices are matched by the FIRST matching pattern in the if-else chain
3. **Check Case Order**: Verify the order of device type cases in bluetooth.cpp
- Earlier cases take precedence over later cases
- Ensure more specific patterns in earlier cases won't prevent your pattern from matching
- Ensure your pattern won't incorrectly match devices intended for other cases
4. **Document Conflicts**: When conflicts exist, verify they are intentional
- More specific patterns earlier in the chain should catch specific devices
- Your pattern should only catch devices not matched by more specific patterns
- Example: "KS-NGCH-X21C" (kingsmithR2Treadmill) should match before "KS-NG-" (horizontreadmill)
5. **Test Pattern Matching**: Consider these scenarios
- Will your pattern match the intended device? (e.g., "KS-NG-X218")
- Will it incorrectly match other devices? (e.g., "KS-NGCH-X21C")
- Are there existing patterns that would match your device first?
**Example Verification Process:**
```bash
# Search for similar patterns
grep -n "KS-" src/devices/bluetooth.cpp
# Review each match for conflicts
# - kingsmithR2Treadmill has "KS-NGCH-X21C" (line 1323)
# - horizontreadmill has "KS-MC" (line 1562)
# - Adding "KS-NG-" to horizontreadmill is safe because:
# 1. "KS-NGCH-" patterns are more specific
# 2. kingsmithR2Treadmill case comes first (line 1312 vs 1560)
# 3. "KS-NG-X218" won't match "KS-NGCH-" patterns
```
**Common Pitfalls:**
- Adding a pattern without checking existing patterns
- Not considering pattern order in the if-else chain
- Adding overly broad patterns that match unintended devices
- Not testing with actual device names
### Characteristics & Protocols
- Bluetooth characteristics handlers in `src/characteristics/`
- FTMS (Fitness Machine Service) protocol support
- ANT+ integration for sensors
- Custom protocol implementations for specific brands
### UI & QML
- QML-based UI with Qt Quick Controls 2
- Main QML files in `src/` (main.qml, settings.qml, etc.)
- Platform-specific UI adaptations (iOS, Android, desktop)
### Integration Features
- Peloton workout/resistance integration (`peloton.cpp`)
- Zwift workout parsing (`zwiftworkout.cpp`)
- GPX file support for route following (`gpx.cpp`)
- Training program support (ZWO, XML formats)
## Platform-Specific Notes
### iOS
- Swift bridge files in `src/ios/`
- Apple Watch integration via `WatchKitConnection.swift`
- HealthKit integration for fitness data
- ConnectIQ SDK for Garmin devices
### Android
- Java bridge files in `src/android/src/`
- ANT+ integration via Android ANT SDK
- Foreground service for background operation
- USB serial support for wired connections
### Windows
- ADB integration for Nordic Track iFit devices
- PaddleOCR integration for Zwift workout detection
- Windows-specific networking features
## File Structure Patterns
### Device Files
```
src/devices/[devicename]/
├── [devicename].h # Header file
├── [devicename].cpp # Implementation
└── README.md # Device-specific documentation (optional)
```
### Test Files
```
tst/Devices/
├── DeviceTestData.h # Test data definitions
├── Test[DeviceName].h # Device-specific test cases
└── TestBluetooth.cpp # Main device detection test suite
```
## Testing Framework
- Uses Google Test (gtest) with Google Mock
- Comprehensive device detection testing
- Configuration-based test scenarios
- XML output support for CI/CD integration
- Tests must be built after main application (links against libqdomyos-zwift.a)
## Configuration & Settings
- Settings managed via `qzsettings.cpp` (QSettings wrapper)
- Platform-specific configuration paths
- Profile system for multiple users/devices
- Extensive customization options for device behavior
## External Dependencies
- Qt 5.15.2+ (Bluetooth, WebSockets, Charts, Quick, etc.)
- Google Test (submodule for testing)
- Platform SDKs (Android ANT+, iOS HealthKit, Windows ADB)
- Protocol Buffers for Zwift API integration
- MQTT client for IoT integration
- Various fitness platform APIs (Strava, Garmin Connect, etc.)
## Adding New ProForm Treadmill Models
This section provides a complete guide for adding new ProForm treadmill models to the codebase, based on the ProForm 995i implementation.
### Prerequisites
1. **Bluetooth Frame Capture File**: A file containing raw Bluetooth frames from the target treadmill
2. **Frame Analysis**: Understanding of which frames are initialization vs. sendPoll frames
3. **BLE Header Knowledge**: Each frame has an 11-byte BLE header that must be removed
### Step-by-Step Implementation Process
#### 1. Process Bluetooth Frames
First, process the raw Bluetooth frames by removing the first 11 bytes (BLE header) from each frame:
```bash
# Example: if you have "proform_model.c" with raw frames
# Process each frame by removing first 11 bytes
# Separate initialization frames from sendPoll frames
```
**Key Requirements:**
- Remove exactly 11 bytes from each frame (BLE header)
- Identify the boundary between initialization and sendPoll frames
- Initialization frames come first, sendPoll frames follow
- Document which packet number starts the sendPoll sequence
#### 2. Add Boolean Flag to Header File
Add the new model flag to `src/devices/proformtreadmill/proformtreadmill.h`:
```cpp
// Add before #ifdef Q_OS_IOS section
bool proform_treadmill_newmodel = false;
```
#### 3. Add Settings Support
Update the following files for settings integration:
**In `src/qzsettings.h`:**
```cpp
static const QString proform_treadmill_newmodel;
static constexpr bool default_proform_treadmill_newmodel = false;
```
**In `src/qzsettings.cpp`:**
```cpp
const QString QZSettings::proform_treadmill_newmodel = QStringLiteral("proform_treadmill_newmodel");
```
* Update the `allSettingsCount` in `qzsettings.cpp`
#### 4. Update QML Settings UI
**In `src/settings.qml`:**
1. Add property at the END of properties list:
```qml
property bool proform_treadmill_newmodel: false
```
2. Update ComboBox model array:
```qml
model: ["Disabled", "Proform New Model", ...]
```
3. Add case selection logic (find next available case number):
```qml
currentIndex: settings.proform_treadmill_newmodel ? XX : 0;
```
4. Add reset logic:
```qml
settings.proform_treadmill_newmodel = false;
```
5. Add switch case:
```qml
case XX: settings.proform_treadmill_newmodel = true; break;
```
#### 5. Implement Device Logic
**In `src/devices/proformtreadmill/proformtreadmill.cpp`:**
1. **Load Settings** (in constructor):
```cpp
proform_treadmill_newmodel = settings.value(QZSettings::proform_treadmill_newmodel, QZSettings::default_proform_treadmill_newmodel).toBool();
```
2. **Add Initialization Case** (in `btinit()` method):
```cpp
} else if (proform_treadmill_newmodel) {
// ALL initialization frames go here
uint8_t initData1[] = {0x00, 0xfe, 0x02, 0x08, 0x02};
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
// ... continue with ALL init frames from capture file
// Use frames from beginning until sendPoll boundary
}
```
3. **Add SendPoll Case** (in `sendPoll()` method):
```cpp
} else if (proform_treadmill_newmodel) {
switch (counterPoll) {
case 0:
// First sendPoll frame
break;
case 1:
// Second sendPoll frame
break;
// ... continue with pattern from sendPoll frames
default:
// Reset counter and cycle
counterPoll = -1;
break;
}
}
```
4. **Update Force Functions** - Add flag to conditional checks in `forceIncline()` and `forceSpeed()`:
```cpp
} else if (proform_treadmill_8_0 || ... || proform_treadmill_newmodel) {
write[14] = write[11] + write[12] + 0x12;
}
```
### Implementation Requirements
#### Frame Processing Rules
- **Exactly 11 bytes** must be removed from each frame (BLE header)
- **All initialization frames** must be included in the btinit() case
- **All sendPoll frames** must be included in the sendPoll() switch statement
- **Frame order** must be preserved exactly as captured
#### Settings Integration Rules
- **Property placement**: Always add new properties at the END of the properties list in settings.qml
- **Case numbering**: Find the next available case number in the ComboBox switch statement
- **Naming convention**: Use descriptive names following existing patterns
#### Code Organization Rules
- **Initialization**: All init frames go in btinit() method
- **Communication**: All sendPoll frames go in sendPoll() method with switch/case structure
- **Force functions**: Add new model flag to existing conditional chains
### Common Pitfalls and Solutions
#### Incorrect Byte Removal
- **Problem**: Removing wrong number of bytes (12 instead of 11)
- **Solution**: Always remove exactly 11 bytes (BLE header)
#### Wrong SendPoll Boundary
- **Problem**: Using initialization frames in sendPoll logic
- **Solution**: Identify exact packet number where sendPoll starts
#### Incomplete Initialization
- **Problem**: Missing initialization frames
- **Solution**: Include ALL frames from start until sendPoll boundary
#### Settings Placement
- **Problem**: Adding property in wrong location in settings.qml
- **Solution**: Always add at END of properties list
### Verification Checklist
- [ ] All 11 bytes removed from each frame
- [ ] Initialization frames correctly identified and included
- [ ] SendPoll frames correctly identified and implemented
- [ ] Settings properly integrated in all required files
- [ ] ComboBox updated with new model option
- [ ] Force functions updated with new model flag
- [ ] Property added at END of settings.qml properties list
### Example Reference
The ProForm 995i implementation serves as the reference example:
- 25 initialization frames (pkt4658-pkt4756)
- 33 sendPoll frames (pkt4761-pkt4897)
- 6-case sendPoll switch statement with cycling logic
- Complete settings integration across all required files
## Development Tips
- Use Qt Creator for development with proper project file support
- The project uses Qt's signal/slot mechanism extensively
- Device implementations should follow existing patterns for consistency
- Add comprehensive logging using the project's logging framework
- Test device detection thoroughly using the existing test infrastructure
- Consider platform differences when adding new features
## Updating Version Numbers
When releasing a new version of QDomyos-Zwift, you must update the version number in **3 files**:
### 1. Android Manifest
**File**: `src/android/AndroidManifest.xml`
Update both `versionName` and `versionCode`:
```xml
<manifest ... android:versionName="X.XX.XX" android:versionCode="XXXX" ...>
```
- `versionName`: The human-readable version (e.g., "2.20.26")
- `versionCode`: Integer build number that must be incremented (e.g., 1274)
### 2. Main QML File
**File**: `src/main.qml`
Update the version text displayed in the UI (around line 938):
```qml
ItemDelegate {
text: "version X.XX.XX"
width: parent.width
}
```
### 3. Qt Project Include File
**File**: `src/qdomyos-zwift.pri`
Update the VERSION variable (around line 1011):
```pri
VERSION = X.XX.XX
```
### Version Numbering Convention
- **Major.Minor.Patch** format (e.g., 2.20.26)
- **Build number** must always increment, never reuse
- Update all 3 files together to keep versions synchronized
### iOS Version (Optional)
iOS version is managed through Xcode project variables:
- `MARKETING_VERSION` in project.pbxproj (corresponds to versionName)
- `CURRENT_PROJECT_VERSION` in project.pbxproj (corresponds to versionCode)
These are typically updated via Xcode IDE rather than manually editing files.
## Additional Memories
- When adding a new setting in QML (setting-tiles.qml), you must:
* Add the property at the END of the properties list

View File

@@ -1,128 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
roberto.viola83@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -1,437 +0,0 @@
/** NimBLE_Server Demo:
*
This is working to broadcast Power and Cadence under the Cycling Power Service Profile
Data tested against Edge and Phone
*
*/
#include <Arduino.h>
#include <NimBLEDevice.h>
short powerInstantaneous = 0;
short cadenceInstantaneous = 0;
short speedInstantaneous = 0;
float powerScale = 1.28; // incoming power is multiplied by this value for correction
short resistance = 0; //Not currently doing anything with this value after receiving it
bool notify = false;
// Define stuff for the Client that will receive data from Fitness Machine
// The remote service we wish to connect to.
static BLEUUID serviceUUID("1826"); // Fitness Machine
// The characteristic of the remote service we are interested in.
static BLEUUID charUUID("2ad2"); // Indoor Bike (Fitness Machine)
static BLEUUID HRserviceUUID("180D"); // HR Service
static BLEUUID HRcharUUID("2a37"); // HR Measuremente
static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic *pRemoteCharacteristic;
static BLEAdvertisedDevice *myDevice;
/*
* Server Stuff
*/
static NimBLEServer *pServer;
/** None of these are required as they will be handled by the library with defaults. **
** Remove as you see fit for your needs */
class ServerCallbacks : public NimBLEServerCallbacks
{
void onConnect(NimBLEServer *pServer)
{
Serial.println("Client connected");
Serial.println("Multi-connect support: start advertising");
NimBLEDevice::startAdvertising();
};
/** Alternative onConnect() method to extract details of the connection.
* See: src/ble_gap.h for the details of the ble_gap_conn_desc struct.
*/
void onConnect(NimBLEServer *pServer, ble_gap_conn_desc *desc)
{
Serial.print("Client address: ");
Serial.println(NimBLEAddress(desc->peer_ota_addr).toString().c_str());
/** We can use the connection handle here to ask for different connection parameters.
* Args: connection handle, min connection interval, max connection interval
* latency, supervision timeout.
* Units; Min/Max Intervals: 1.25 millisecond increments.
* Latency: number of intervals allowed to skip.
* Timeout: 10 millisecond increments, try for 5x interval time for best results.
*/
pServer->updateConnParams(desc->conn_handle, 24, 48, 0, 60);
};
void onDisconnect(NimBLEServer *pServer)
{
Serial.println("Client disconnected - start advertising");
NimBLEDevice::startAdvertising();
};
void onMTUChange(uint16_t MTU, ble_gap_conn_desc *desc)
{
Serial.printf("MTU updated: %u for connection ID: %u\n", MTU, desc->conn_handle);
};
};
/** Handler class for characteristic actions */
class CharacteristicCallbacks : public NimBLECharacteristicCallbacks
{
void onRead(NimBLECharacteristic *pCharacteristic)
{
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(": onRead(), value: ");
Serial.println(pCharacteristic->getValue().c_str());
};
void onWrite(NimBLECharacteristic *pCharacteristic)
{
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(": onWrite(), value: ");
Serial.println(pCharacteristic->getValue().c_str());
};
/** Called before notification or indication is sent,
* the value can be changed here before sending if desired.
*/
void onNotify(NimBLECharacteristic *pCharacteristic)
{
Serial.println("Sending notification to clients");
};
/** The status returned in status is defined in NimBLECharacteristic.h.
* The value returned in code is the NimBLE host return code.
*/
void onStatus(NimBLECharacteristic *pCharacteristic, Status status, int code)
{
String str = ("Notification/Indication status code: ");
str += status;
str += ", return code: ";
str += code;
str += ", ";
str += NimBLEUtils::returnCodeToString(code);
Serial.println(str);
};
void onSubscribe(NimBLECharacteristic *pCharacteristic, ble_gap_conn_desc *desc, uint16_t subValue)
{
String str = "Client ID: ";
str += desc->conn_handle;
str += " Address: ";
str += std::string(NimBLEAddress(desc->peer_ota_addr)).c_str();
if (subValue == 0)
{
str += " Unsubscribed to ";
}
else if (subValue == 1)
{
str += " Subscribed to notifications for ";
}
else if (subValue == 2)
{
str += " Subscribed to indications for ";
}
else if (subValue == 3)
{
str += " Subscribed to notifications and indications for ";
}
str += std::string(pCharacteristic->getUUID()).c_str();
Serial.println(str);
};
};
/** Handler class for descriptor actions */
class DescriptorCallbacks : public NimBLEDescriptorCallbacks
{
void onWrite(NimBLEDescriptor *pDescriptor)
{
std::string dscVal((char *)pDescriptor->getValue(), pDescriptor->getLength());
Serial.print("Descriptor witten value:");
Serial.println(dscVal.c_str());
};
void onRead(NimBLEDescriptor *pDescriptor)
{
Serial.print(pDescriptor->getUUID().toString().c_str());
Serial.println(" Descriptor read");
};
};
/*
* Client Stuff
*/
// This callback is for when data is received from Server
static void notifyCallback(
BLERemoteCharacteristic *pBLERemoteCharacteristic,
uint8_t *pData,
size_t length,
bool isNotify)
{
powerInstantaneous = pData[8] | pData[9] << 8; // 2 bytes of power
cadenceInstantaneous = 60; //(pData[4] | pData[5] << 8) / 2; // 2 bytes of power in 0.5 resolution RPM, convert to RPM
resistance = pData[6]; // 1 byte of resistance
Serial.printf("Power = %d | Cadence = %d | Resistance = %d\n", powerInstantaneous, cadenceInstantaneous, resistance);
}
/** None of these are required as they will be handled by the library with defaults. **
** Remove as you see fit for your needs */
class MyClientCallback : public BLEClientCallbacks
{
void onConnect(BLEClient *pclient)
{
}
void onDisconnect(BLEClient *pclient)
{
connected = false;
Serial.println("onDisconnect");
}
};
bool connectToServer()
{
Serial.print("Forming a connection to ");
Serial.println(myDevice->getAddress().toString().c_str());
BLEClient *pClient = BLEDevice::createClient();
Serial.println(" - Created client");
pClient->setClientCallbacks(new MyClientCallback());
// Connect to the remove BLE Server.
pClient->connect(myDevice); // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
Serial.println(" - Connected to server");
// Obtain a reference to the service we are after in the remote BLE server.
BLERemoteService *pRemoteService = pClient->getService(serviceUUID);
if (pRemoteService == nullptr)
{
Serial.print("Failed to find our service UUID: ");
Serial.println(serviceUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found our service");
// Obtain a reference to the characteristic in the service of the remote BLE server.
pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
if (pRemoteCharacteristic == nullptr)
{
Serial.print("Failed to find our characteristic UUID: ");
Serial.println(charUUID.toString().c_str());
pClient->disconnect();
return false;
}
Serial.println(" - Found our characteristic");
// Read the value of the characteristic.
if (pRemoteCharacteristic->canRead())
{
std::string value = pRemoteCharacteristic->readValue();
Serial.print("The characteristic value was: ");
Serial.println(value.c_str());
}
if (pRemoteCharacteristic->canNotify())
pRemoteCharacteristic->registerForNotify(notifyCallback);
connected = true;
return true;
}
/**
* Scan for BLE servers and find the first one that advertises the service we are looking for.
*/
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks
{
/**
* Called for each advertising BLE server.
*/
/*** Only a reference to the advertised device is passed now
void onResult(BLEAdvertisedDevice advertisedDevice) { **/
void onResult(BLEAdvertisedDevice *advertisedDevice)
{
Serial.print("BLE Advertised Device found: ");
Serial.println(advertisedDevice->toString().c_str());
// We have found a device, let us now see if it contains the service we are looking for.
/********************************************************************************
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
********************************************************************************/
if (advertisedDevice->haveServiceUUID() && advertisedDevice->isAdvertisingService(serviceUUID))
{
BLEDevice::getScan()->stop();
/*******************************************************************
myDevice = new BLEAdvertisedDevice(advertisedDevice);
*******************************************************************/
myDevice = advertisedDevice; /** Just save the reference now, no need to copy the object */
doConnect = true;
doScan = true;
} // Found our server
} // onResult
}; // MyAdvertisedDeviceCallbacks
//delays for X ms, should not block execution
void softDelay(unsigned long delayTime)
{
unsigned long startTime = millis();
while ((millis() - startTime) < delayTime)
{
//wait
}
}
/** Define callback instances globally to use for multiple Characteristics \ Descriptors */
// This section is for the Server that will broadcast the data as Cycling Power
static DescriptorCallbacks dscCallbacks;
static CharacteristicCallbacks chrCallbacks;
NimBLECharacteristic *CyclingPowerFeature = NULL;
NimBLECharacteristic *CyclingPowerMeasurement = NULL;
NimBLECharacteristic *CyclingPowerSensorLocation = NULL;
NimBLECharacteristic *HRMeasurement = NULL;
unsigned char bleBuffer[8];
unsigned char slBuffer[1];
unsigned char fBuffer[4];
unsigned short revolutions = 0;
unsigned short timestamp = 0;
unsigned short flags = 0x20;
byte sensorlocation = 0x0D;
long lastNotify = 0;
long lastRevolution = 0;
void setup()
{
Serial.begin(115200);
Serial.println("Starting NimBLE Server");
/** sets device name */
NimBLEDevice::init("QZESP");
/** Optional: set the transmit power, default is 3db */
NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */
pServer = NimBLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
fBuffer[0] = 0x00;
fBuffer[1] = 0x00;
fBuffer[2] = 0x00;
fBuffer[3] = 0x08;
slBuffer[0] = sensorlocation & 0xff;
NimBLEService *pDeadService = pServer->createService("1818");
CyclingPowerFeature = pDeadService->createCharacteristic(
"2A65",
NIMBLE_PROPERTY::READ);
CyclingPowerSensorLocation = pDeadService->createCharacteristic(
"2A5D",
NIMBLE_PROPERTY::READ);
CyclingPowerMeasurement = pDeadService->createCharacteristic(
"2A63",
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
CyclingPowerFeature->setValue(fBuffer, 4);
CyclingPowerSensorLocation->setValue(slBuffer, 1);
CyclingPowerMeasurement->setValue(slBuffer, 1);
/** Start the services when finished creating all Characteristics and Descriptors */
pDeadService->start();
#if 0
// HR service
NimBLEService *pHRService = pServer->createService("180D");
HRMeasurement = pHRService->createCharacteristic(
"2A37",
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
HRMeasurement->setValue(fBuffer, 2);
/** Start the services when finished creating all Characteristics and Descriptors */
pHRService->start();
#endif
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
/** Add the services to the advertisement data **/
// pAdvertising->addServiceUUID(pHRService->getUUID());
pAdvertising->addServiceUUID(pDeadService->getUUID());
pAdvertising->setScanResponse(true);
pAdvertising->start();
Serial.println("Advertising Started");
Serial.println("Starting Arduino BLE Client application...");
BLEDevice::init("");
// Retrieve a Scanner and set the callback we want to use to be informed when we
// have detected a new device. Specify that we want active scanning and start the
// scan to run for 5 seconds.
BLEScan *pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setInterval(1349);
pBLEScan->setWindow(449);
pBLEScan->setActiveScan(true);
pBLEScan->start(5, false);
}
void loop()
{
// If the flag "doConnect" is true then we have scanned for and found the desired
// BLE Server with which we wish to connect. Now we connect to it. Once we are
// connected we set the connected flag to be true.
if (doConnect == true)
{
if (connectToServer())
{
Serial.println("We are now connected to the BLE Server.");
}
else
{
Serial.println("We have failed to connect to the server; there is nothing more we will do.");
}
doConnect = false;
}
// If we are connected to a peer BLE Server, update the characteristic each time we are reached
// with the current time since boot.
if (connected)
{
//Stuff to do when connected to Client
}
else if (doScan)
{
BLEDevice::getScan()->start(0); // this is just sample to start scan after disconnect, most likely there is better way to do it in arduino
}
// convert RPM to timestamp
if (cadenceInstantaneous != 0 && (millis()) >= (lastRevolution + (60000 / cadenceInstantaneous)))
{
revolutions++; // One crank revolution should have passed, add one revolution
timestamp = (unsigned short)(((millis() * 1024) / 1000) % 65536); // create timestamp and format
lastRevolution = millis();
}
if (millis() - lastNotify >= 1000) // do this every second
{
//if (pServer->getConnectedCount() > 0)
{
bleBuffer[0] = flags & 0xff;
bleBuffer[1] = (flags >> 8) & 0xff;
bleBuffer[2] = powerInstantaneous & 0xff;
bleBuffer[3] = (powerInstantaneous >> 8) & 0xff;
bleBuffer[4] = revolutions & 0xff;
bleBuffer[5] = (revolutions >> 8) & 0xff;
bleBuffer[6] = timestamp & 0xff;
bleBuffer[7] = (timestamp >> 8) & 0xff;
CyclingPowerMeasurement->setValue(bleBuffer, 8);
CyclingPowerMeasurement->notify();
/*bleBuffer[0] = 0;
bleBuffer[1] = powerInstantaneous;
HRMeasurement->setValue(bleBuffer, 2);
HRMeasurement->notify();*/
lastNotify = millis();
}
}
/*if (pServer->getConnectedCount() == 0)
{
powerInstantaneous = 0;
}*/
}

View File

@@ -1,53 +0,0 @@
#include <NimBLEDevice.h>
#define INDOOR_BIKE_DATA_UUID "00002AD2-0000-1000-8000-00805f9b34fb"
#define CUSTOM_SERVICE_UUID "ce060000-43e5-11e4-916c-0800200c9a66"
NimBLEServer* pServer = nullptr;
NimBLECharacteristic* pIndoorBikeDataChar = nullptr;
class ServerCallbacks: public NimBLEServerCallbacks {
void onConnect(NimBLEServer* pServer) {
Serial.println("Client connected");
};
void onDisconnect(NimBLEServer* pServer) {
Serial.println("Client disconnected");
}
};
void setup() {
Serial.begin(115200);
Serial.println("Starting NimBLE Server");
NimBLEDevice::init("PM5 431431183 Row");
pServer = NimBLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
NimBLEService* pFtmService = pServer->createService("1826");
//NimBLEService* pCustomService = pServer->createService(CUSTOM_SERVICE_UUID);
pIndoorBikeDataChar = pFtmService->createCharacteristic(
INDOOR_BIKE_DATA_UUID,
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::NOTIFY
);
pFtmService->start();
//pCustomService->start();
NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->addServiceUUID(pFtmService->getUUID());
//pAdvertising->addServiceUUID(CUSTOM_SERVICE_UUID);
const std::string data = { 0x01, 0x10, 0x00 }; // Imposta i valori desiderati
pAdvertising->setServiceData(pFtmService->getUUID(), data);
pAdvertising->start();
Serial.println("Advertising started");
}
void loop() {
// Metti qui il tuo codice principale, da eseguire ripetutamente
// Ad esempio, potresti aggiornare il valore della caratteristica Indoor Bike Data
}

133
README.md
View File

@@ -1,131 +1,62 @@
# qdomyos-zwift
Zwift bridge for Treadmills and Bike!
## QZ is not affiliated with or endorsed by any subscription service or maker of exercise equipment.
[<img src="docs/img/google_play.png">](https://play.google.com/store/apps/details?id=org.cagnulen.qdomyoszwift&fbclid=IwAR3CVoYb0scvGf7gb0Y20VFh5Na5fDWwe7VACk-2c45Tm0x5s8sXpIGhGyw)
[<img src="docs/img/app_store.png">](https://apps.apple.com/app/id1543684531?fbclid=IwAR10H6y3mEgwkTlGJON3e8voYOh2wt3kLFOpFzoIXaYZ_N0y0pDvKxHMUaM)
<a href="https://www.buymeacoffee.com/cagnulein" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
<table>
<tr>
<td>
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot1.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
</td>
<td>
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot2.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
</td>
<td>
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot3.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
</td>
<td>
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot4.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
</td>
<td>
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot5.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
</td>
</tr>
</table>
![UI](docs/img/treadmill-bridge-schema.png)
[![Video](https://img.youtube.com/vi/GgG3dMhmo2Y/0.jpg)](https://www.youtube.com/watch?v=GgG3dMhmo2Y)
![UI](docs/img/ui.png)
![UI](docs/img/realtime-chart.png)
UI on Linux
![UI](docs/img/ui-mac.png)
UI on MacOS
### Features
# UI Features
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|:---|:---:|:---:|:---:|:---:|---:|
|Tiles Customization|X|X|X|X|Order and visibility of each tile|
|Profiles|X|X|X|X|Different user or different fitness device profiles|
|UI Zoom Customization|X|X|X|X||
# Peloton Features
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|:---|:---:|:---:|:---:|:---:|---:|
|Bike metrics on the peloton app|X||X|||
|Power zone with auto resistance|X|||||
|Peloton real-time resistance conversion|X||X||with the possibility to customize it|
|Peloton real-time auto-resistance|X||X||with the possibility to customize it|
|Peloton auto speed and auto inclination||X|X||with the possibility to customize it|
# Heart Rate Features
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|:---|:---:|:---:|:---:|:---:|---:|
|Heart Rate support|X|X|X|X|Apple Watch, ANT+ devices and Bluetooth devices|
|Heart Rate Zones Customizations|X|X|X|X||
|Ability to calculate Wattage from HR and Cadence|X||||for the bikes that doesn't have a power sensor|
# 3rd Apps Compatibility
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|:---|:---:|:---:|:---:|:---:|---:|
|Zwift Compatibility|X|X|X|X||
|Zwift Auto resistance|X||X|||
|Zwift Auto inclination and speed||X|X||https://www.youtube.com/watch?v=KTQ2n7yeDbo|
|Wahoo RGT Compatibility|X|X|X|X||
|VzFit Compatibility|X|X|X|X||
|Rouvy Compatibility|X|X|X|X||
|IFIT app Compatibility|X|||||
|Echelon app Compatibility|X|||||
|Wahoo Dircon Compatibility|X|X|X|X|in order to send data to Zwift or RGT with Wifi only!|
|One device only support for Zwift and Wahoo RGT|X|X|X|X|using Wahoo Dircon https://www.youtube.com/watch?v=gYYUXNWFAok|
|BitGym Compatibility|X|X|X|X||
# Training Program
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|:---|:---:|:---:|:---:|:---:|---:|
|Builtin video support (Kinomap like)|X|X|X|X|Files could be local or on the cloud!|
|GPX auto following|X|X|X|X||
|2D/3D maps for GPX|X|X|X|X||
|ZWO (Zwift workout file) compatibility|X|X|X|X||
|XML Workout file compatibility|X|X|X|X||
|Auto follow workout based on your heart rate|X|X|X|X||
|Random workout|X|X|X|X||
# Statistics
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|:---|:---:|:---:|:---:|:---:|---:|
|E-Mail report|X|X|X|X|at the end of the workout|
|Strava integration|X|X|X|X|press stop at the end of the workout to auto upload it|
# Misc
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|:---|:---:|:---:|:---:|:---:|---:|
|Resistance shifting with bluetooth remote|X||X|||
|TTS support|X|X|X|X||
|Zwift Play & Click support|X|||||
|MQTT integration|X|X|X|X||
|OpenSoundControl integration|X|X|X|X||
1. Domyos compatible
2. Toorx TRX Route Key compatible
3. Echelon Connect Sport compatible
4. Zwift compatible
5. Create, load and save train programs
6. Measure distance, elevation gain and watts
7. Gpx import (with difficulty slider)
8. Realtime Charts
![First Success](docs/img/first_success.jpg)
### Installation
You can install it on multiple platforms.
Read the [installation procedure](docs/10_Installation.md)
You can install on multiple platforms.
Read the [installation procedure](docs/10_Installation.md)
### Tested on
The QDomyos-Zwift application can run on [Macintosh or Linux devices](docs/10_Installation.md) iOS, and Android.
It supports any [FTMS-compatible application](docs/20_supported_devices_and_applications.md) software and most [bluetooth enabled device](docs/20_supported_devices_and_applications.md).
You can run the app on [Macintosh or Linux devices](docs/10_Installation.md). IOS and Android are also supported.
### No GUI version
QDomyos-Zwift works on every [FTMS-compatible application](docs/20_supported_devices_and_applications.md), and virtually any [bluetooth enabled device](docs/20_supported_devices_and_applications.md).
### No gui version
run as
$ sudo ./qdomyos-zwift -no-gui
$ sudo ./qdomyos-zwift -no-gui
### Reference
=> GitHub Repository: [QDomyos-Zwift on GitHub](https://github.com/ProH4Ck/treadmill-bridge)
https://github.com/ProH4Ck/treadmill-bridge
=> Treadmill Incline Reference: [What Is 10 Degrees in Incline on a Treadmill?](https://www.livestrong.com/article/422012-what-is-10-degrees-in-incline-on-a-treadmill/)
https://www.livestrong.com/article/422012-what-is-10-degrees-in-incline-on-a-treadmill/
=> Icon Attribution: Icons used in this documentation are from [Flaticon.com](https://www.flaticon.com)
Icons used in this documentation comes from [flaticon.com](https://www.flaticon.com)
### Blog
=> Related Blog: [Roberto Viola's Blog](https://robertoviola.cloud)
https://robertoviola.cloud

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
<key>NSSupportsLiveActivities</key>
<true/>
</dict>
</plist>

View File

@@ -1,84 +0,0 @@
//
// QZWidget.swift
// QZWidget
//
// Created by Roberto Viola on 04/10/25.
//
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), emoji: "😀")
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), emoji: "😀")
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, emoji: "😀")
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
// func relevances() async -> WidgetRelevances<Void> {
// // Generate a list containing the contexts this widget is relevant in.
// }
}
struct SimpleEntry: TimelineEntry {
let date: Date
let emoji: String
}
struct QZWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
Text("Time:")
Text(entry.date, style: .time)
Text("Emoji:")
Text(entry.emoji)
}
}
}
struct QZWidget: Widget {
let kind: String = "QZWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
if #available(iOS 17.0, *) {
QZWidgetEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
} else {
QZWidgetEntryView(entry: entry)
.padding()
.background()
}
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
#Preview(as: .systemSmall) {
QZWidget()
} timeline: {
SimpleEntry(date: .now, emoji: "😀")
SimpleEntry(date: .now, emoji: "🤩")
}

View File

@@ -1,18 +0,0 @@
//
// QZWidgetBundle.swift
// QZWidget
//
// Created by Roberto Viola on 04/10/25.
//
import WidgetKit
import SwiftUI
@main
struct QZWidgetBundle: WidgetBundle {
var body: some Widget {
QZWidget()
QZWidgetControl()
QZWidgetLiveActivity()
}
}

View File

@@ -1,54 +0,0 @@
//
// QZWidgetControl.swift
// QZWidget
//
// Created by Roberto Viola on 04/10/25.
//
import AppIntents
import SwiftUI
import WidgetKit
struct QZWidgetControl: ControlWidget {
var body: some ControlWidgetConfiguration {
StaticControlConfiguration(
kind: "org.cagnulein.qdomyoszwift.QZWidget",
provider: Provider()
) { value in
ControlWidgetToggle(
"Start Timer",
isOn: value,
action: StartTimerIntent()
) { isRunning in
Label(isRunning ? "On" : "Off", systemImage: "timer")
}
}
.displayName("Timer")
.description("A an example control that runs a timer.")
}
}
extension QZWidgetControl {
struct Provider: ControlValueProvider {
var previewValue: Bool {
false
}
func currentValue() async throws -> Bool {
let isRunning = true // Check if the timer is running
return isRunning
}
}
}
struct StartTimerIntent: SetValueIntent {
static let title: LocalizedStringResource = "Start a timer"
@Parameter(title: "Timer is running")
var value: Bool
func perform() async throws -> some IntentResult {
// Start / stop the timer based on `value`.
return .result()
}
}

View File

@@ -1,174 +0,0 @@
//
// QZWidgetLiveActivity.swift
// QDomyos-Zwift Live Activity Widget
//
// Displays workout metrics on Dynamic Island and Lock Screen
//
import ActivityKit
import WidgetKit
import SwiftUI
// QZWorkoutAttributes is defined in QZWorkoutAttributes.swift (shared file)
// MARK: - Live Activity Widget
@available(iOS 16.1, *)
struct QZWidgetLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: QZWorkoutAttributes.self) { context in
// Lock screen/banner UI
LockScreenLiveActivityView(context: context)
} dynamicIsland: { context in
DynamicIsland {
// Expanded UI
DynamicIslandExpandedRegion(.leading) {
VStack(alignment: .leading, spacing: 4) {
let speed = context.attributes.useMiles ? context.state.speed * 0.621371 : context.state.speed
let speedUnit = context.attributes.useMiles ? "mph" : "km/h"
Label("\(Int(speed)) \(speedUnit)", systemImage: "speedometer")
.font(.caption)
Label("\(context.state.heartRate) bpm", systemImage: "heart.fill")
.font(.caption)
.foregroundColor(.red)
}
}
DynamicIslandExpandedRegion(.trailing) {
VStack(alignment: .trailing, spacing: 4) {
Label("\(Int(context.state.power)) W", systemImage: "bolt.fill")
.font(.caption)
.foregroundColor(.yellow)
Label("\(Int(context.state.cadence)) rpm", systemImage: "arrow.clockwise")
.font(.caption)
}
}
DynamicIslandExpandedRegion(.center) {
// Empty or can add more info
}
DynamicIslandExpandedRegion(.bottom) {
HStack {
let distanceKm = context.state.distance / 1000.0
let distance = context.attributes.useMiles ? distanceKm * 0.621371 : distanceKm
let distanceUnit = context.attributes.useMiles ? "mi" : "km"
Label(String(format: "%.2f \(distanceUnit)", distance), systemImage: "map")
Spacer()
Label("\(Int(context.state.kcal)) kcal", systemImage: "flame.fill")
.foregroundColor(.orange)
}
.font(.caption)
.padding(.horizontal)
}
} compactLeading: {
// Compact leading (left side of Dynamic Island)
HStack(spacing: 2) {
Image(systemName: "heart.fill")
.foregroundColor(.red)
Text("\(context.state.heartRate)")
.font(.caption2)
}
} compactTrailing: {
// Compact trailing (right side of Dynamic Island)
HStack(spacing: 2) {
Image(systemName: "bolt.fill")
.foregroundColor(.yellow)
Text("\(Int(context.state.power))")
.font(.caption2)
}
} minimal: {
// Minimal view (when multiple activities)
Image(systemName: "figure.run")
}
}
}
}
// MARK: - Lock Screen View
@available(iOS 16.1, *)
struct LockScreenLiveActivityView: View {
let context: ActivityViewContext<QZWorkoutAttributes>
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Image(systemName: "figure.indoor.cycle")
.foregroundColor(.blue)
Text(context.attributes.deviceName)
.font(.headline)
Spacer()
}
HStack(spacing: 16) {
let speed = context.attributes.useMiles ? context.state.speed * 0.621371 : context.state.speed
let speedUnit = context.attributes.useMiles ? "mph" : "km/h"
MetricView(icon: "speedometer", value: String(format: "%.1f", speed), unit: speedUnit)
MetricView(icon: "heart.fill", value: "\(context.state.heartRate)", unit: "bpm", color: .red)
MetricView(icon: "bolt.fill", value: "\(Int(context.state.power))", unit: "W", color: .yellow)
}
HStack(spacing: 16) {
let distanceKm = context.state.distance / 1000.0
let distance = context.attributes.useMiles ? distanceKm * 0.621371 : distanceKm
let distanceUnit = context.attributes.useMiles ? "mi" : "km"
MetricView(icon: "arrow.clockwise", value: "\(Int(context.state.cadence))", unit: "rpm")
MetricView(icon: "map", value: String(format: "%.2f", distance), unit: distanceUnit)
MetricView(icon: "flame.fill", value: "\(Int(context.state.kcal))", unit: "kcal", color: .orange)
}
}
.padding()
}
}
// MARK: - Metric View Component
struct MetricView: View {
let icon: String
let value: String
let unit: String
var color: Color = .primary
var body: some View {
VStack(spacing: 2) {
Image(systemName: icon)
.foregroundColor(color)
.font(.caption)
Text(value)
.font(.system(.body, design: .rounded))
.fontWeight(.semibold)
Text(unit)
.font(.caption2)
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity)
}
}
// MARK: - Preview
@available(iOS 16.1, *)
struct QZWidgetLiveActivity_Previews: PreviewProvider {
static let attributes = QZWorkoutAttributes(deviceName: "QZ Bike", useMiles: false)
static let contentState = QZWorkoutAttributes.ContentState(
speed: 25.5,
cadence: 85,
power: 200,
heartRate: 145,
distance: 12500, // meters (will be displayed as 12.50 km or 7.77 mi)
kcal: 320,
useMiles: false
)
static var previews: some View {
attributes
.previewContext(contentState, viewKind: .dynamicIsland(.compact))
.previewDisplayName("Island Compact")
attributes
.previewContext(contentState, viewKind: .dynamicIsland(.expanded))
.previewDisplayName("Island Expanded")
attributes
.previewContext(contentState, viewKind: .dynamicIsland(.minimal))
.previewDisplayName("Minimal")
attributes
.previewContext(contentState, viewKind: .content)
.previewDisplayName("Lock Screen")
}
}

View File

@@ -1,41 +0,0 @@
//
// QZWorkoutAttributes.swift
// QDomyos-Zwift
//
// Shared attributes for Live Activities
// MUST be included in both qdomyoszwift and QZWidget targets
//
import Foundation
import ActivityKit
@available(iOS 16.1, *)
public struct QZWorkoutAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
public var speed: Double
public var cadence: Double
public var power: Double
public var heartRate: Int
public var distance: Double
public var kcal: Double
public var useMiles: Bool
public init(speed: Double, cadence: Double, power: Double, heartRate: Int, distance: Double, kcal: Double, useMiles: Bool) {
self.speed = speed
self.cadence = cadence
self.power = power
self.heartRate = heartRate
self.distance = distance
self.kcal = kcal
self.useMiles = useMiles
}
}
public var deviceName: String
public var useMiles: Bool
public init(deviceName: String, useMiles: Bool) {
self.deviceName = deviceName
self.useMiles = useMiles
}
}

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.cagnulein.qdomyoszwift</string>
</array>
</dict>
</plist>

View File

@@ -1,285 +0,0 @@
#!/bin/bash
set -e
echo "=== QDomyos-Zwift CI Post Clone Script ==="
echo "Installing Qt 5.15.2 EXACTLY and preparing environment"
# Exit if not on macOS (sanity check)
if [[ "$OSTYPE" != "darwin"* ]]; then
echo "ERROR: This script must run on macOS"
exit 1
fi
# Apply Xcode Cloud network workarounds
export HOMEBREW_NO_AUTO_UPDATE=1
export GIT_HTTP_MAX_REQUESTS=1
# Check if Qt 5.15.2 is already installed
if command -v qmake &> /dev/null; then
QT_VERSION=$(qmake -v | grep -o "5\.[0-9]*\.[0-9]*" | head -1)
if [[ "$QT_VERSION" == "5.15.2" ]]; then
echo "Qt 5.15.2 already installed - PERFECT!"
export QT_DIR=$(dirname $(dirname $(which qmake)))
export PATH="$QT_DIR/bin:$PATH"
# CRITICAL: Save Qt path to persistent file for next script
echo "Saving existing Qt installation path for ci_pre_xcodebuild.sh..."
echo "export QT_DIR=\"$QT_DIR\"" > /tmp/qt_env.sh
echo "export PATH=\"$QT_DIR/bin:/tmp/Qt-5.15.2/ios/bin:/private/tmp/Qt-5.15.2/ios/bin:\$PATH\"" >> /tmp/qt_env.sh
chmod +x /tmp/qt_env.sh
else
echo "WRONG Qt version found: $QT_VERSION"
echo "MUST install Qt 5.15.2 exactly"
# Uninstall wrong version
brew uninstall --ignore-dependencies qt@5 qt || echo "No Qt to uninstall"
fi
fi
# Force install Qt 5.15.2 EXACTLY
if ! command -v qmake &> /dev/null || [[ "$(qmake -v | grep -o "5\.[0-9]*\.[0-9]*" | head -1)" != "5.15.2" ]]; then
echo "Installing Qt 5.15.2 EXACTLY - NO OTHER VERSION ACCEPTED"
# Method 1: Use aqt (Another Qt Installer) to get exact version
echo "Installing aqt (Another Qt Installer) for exact Qt version control..."
python3 -m pip install aqt || echo "aqt installation failed, trying homebrew method"
if command -v aqt &> /dev/null; then
echo "Using aqt to install Qt 5.15.2 exactly..."
aqt install-qt mac desktop 5.15.2 --outputdir /usr/local/Qt
export QT_DIR="/usr/local/Qt/5.15.2/clang_64"
export PATH="$QT_DIR/bin:$PATH"
# CRITICAL: Save Qt path to persistent file for next script
echo "Saving aqt Qt installation path for ci_pre_xcodebuild.sh..."
echo "export QT_DIR=\"/usr/local/Qt/5.15.2/clang_64\"" > /tmp/qt_env.sh
echo "export PATH=\"/usr/local/Qt/5.15.2/clang_64/bin:/tmp/Qt-5.15.2/ios/bin:/private/tmp/Qt-5.15.2/ios/bin:\$PATH\"" >> /tmp/qt_env.sh
chmod +x /tmp/qt_env.sh
else
echo "aqt failed, using precompiled Qt 5.15.2 from GitHub..."
# Download precompiled Qt 5.15.2 from your GitHub release
echo "Downloading precompiled Qt 5.15.2 from GitHub..."
cd /tmp
curl -L "https://github.com/cagnulein/qt5.15.2/releases/download/qt-5.15.2/qt-5.15.2.tar.xz" -o qt-5.15.2.tar.xz
if [[ -f "qt-5.15.2.tar.xz" ]]; then
echo "Extracting precompiled Qt 5.15.2..."
tar -mxf qt-5.15.2.tar.xz
cd 5.15.2 || { echo "Extraction failed or directory not found"; exit 1; }
# Debug: Check extraction result
echo "Contents after extraction:"
ls -la
# Install to temp location (no sudo needed)
echo "Setting up Qt 5.15.2..."
mkdir -p /tmp/Qt-5.15.2
# Files are extracted directly - copy Qt directories
echo "Files extracted directly, copying Qt directories..."
# Copy the Qt directories we need
if [[ -d "ios" ]]; then
cp -R ios /tmp/Qt-5.15.2/
echo "Copied ios directory"
fi
if [[ -d "clang_64" ]]; then
cp -R clang_64 /tmp/Qt-5.15.2/
echo "Copied clang_64 directory"
fi
if [[ -d "qthttpserver" ]]; then
cp -R qthttpserver /tmp/Qt-5.15.2/
echo "Copied qthttpserver directory"
fi
if [[ -f "sha1s.txt" ]]; then
cp sha1s.txt /tmp/Qt-5.15.2/
echo "Copied sha1s.txt"
fi
# Set environment for iOS development - support both /tmp and /private/tmp
export QT_DIR="/tmp/Qt-5.15.2/ios"
export PATH="$QT_DIR/bin:$PATH"
# CRITICAL: Save Qt path to persistent file for next script
echo "Saving Qt installation path for ci_pre_xcodebuild.sh..."
echo "export QT_DIR=\"/tmp/Qt-5.15.2/ios\"" > /tmp/qt_env.sh
echo "export PATH=\"/tmp/Qt-5.15.2/ios/bin:/private/tmp/Qt-5.15.2/ios/bin:\$PATH\"" >> /tmp/qt_env.sh
chmod +x /tmp/qt_env.sh
echo "Qt 5.15.2 precompiled installation completed"
# CRITICAL: Fix hardcoded paths in .pri files
# The Qt archive contains .pri files with absolute paths from local machine
# Replace them with the Xcode Cloud installation path
echo "Fixing hardcoded paths in Qt .pri files..."
find /tmp/Qt-5.15.2 -name "*.pri" -type f -exec sed -i '' 's|/Users/cagnulein/Qt/5.15.2|/tmp/Qt-5.15.2|g' {} \;
find /tmp/Qt-5.15.2 -name "*.pri" -type f -exec sed -i '' 's|/Users/cagnulein/Qt/5.15.2|/private/tmp/Qt-5.15.2|g' {} \;
echo "Fixed paths in .pri files"
# CRITICAL: Download missing qmldbg libraries
echo "Downloading missing qmldbg libraries..."
cd /tmp
# Download libqmldbg_debugger.a
echo "Downloading libqmldbg_debugger.a.zip..."
curl -L -o libqmldbg_debugger.a.zip https://github.com/cagnulein/qt5.15.2/releases/download/qt-5.15.2/libqmldbg_debugger.a.zip
unzip -o libqmldbg_debugger.a.zip
# Download libqmldbg_nativedebugger.a (from the old zip)
echo "Downloading libqmldbg_nativedebugger.zip..."
curl -L -o libqmldbg_nativedebugger.zip https://github.com/cagnulein/qt5.15.2/releases/download/qt-5.15.2/libqmldbg_debugger.zip
unzip -o libqmldbg_nativedebugger.zip
echo "Contents after extraction:"
ls -la libqmldbg*.a 2>/dev/null || echo "No .a files found in current directory"
# Ensure target directory exists
mkdir -p /tmp/Qt-5.15.2/ios/plugins/qmltooling
# Move libqmldbg_debugger.a
if [[ -f "libqmldbg_debugger.a" ]]; then
mv libqmldbg_debugger.a /tmp/Qt-5.15.2/ios/plugins/qmltooling/
echo "SUCCESS: Moved libqmldbg_debugger.a"
else
echo "FATAL ERROR: libqmldbg_debugger.a not found after extraction"
exit 1
fi
# Move libqmldbg_nativedebugger.a (rename from _debug version if needed)
if [[ -f "libqmldbg_nativedebugger.a" ]]; then
mv libqmldbg_nativedebugger.a /tmp/Qt-5.15.2/ios/plugins/qmltooling/
echo "SUCCESS: Moved libqmldbg_nativedebugger.a"
elif [[ -f "libqmldbg_nativedebugger_debug.a" ]]; then
# Use debug version as fallback (better than nothing)
mv libqmldbg_nativedebugger_debug.a /tmp/Qt-5.15.2/ios/plugins/qmltooling/libqmldbg_nativedebugger.a
echo "WARNING: Used libqmldbg_nativedebugger_debug.a as fallback"
else
echo "FATAL ERROR: libqmldbg_nativedebugger.a not found after extraction"
exit 1
fi
echo "Installed missing qmldbg libraries"
rm -f libqmldbg_debugger.a.zip libqmldbg_nativedebugger.zip
# Verify httpserver module is now findable
if [[ -f "/tmp/Qt-5.15.2/ios/mkspecs/modules-inst/qt_lib_httpserver.pri" ]]; then
echo "SUCCESS: httpserver module .pri file found"
grep "QT.httpserver.libs" /tmp/Qt-5.15.2/ios/mkspecs/modules-inst/qt_lib_httpserver.pri | head -1
else
echo "WARNING: httpserver .pri file not found at expected location"
find /tmp/Qt-5.15.2 -name "*httpserver*.pri" 2>/dev/null || echo "No httpserver .pri files found"
fi
else
echo "ERROR: Failed to download precompiled Qt from GitHub"
exit 1
fi
fi
fi
# MANDATORY verification - FAIL if not 5.15.2
echo "MANDATORY Qt 5.15.2 verification..."
if command -v qmake &> /dev/null; then
QT_VERSION=$(qmake -v | grep -o "5\.[0-9]*\.[0-9]*" | head -1)
if [[ "$QT_VERSION" != "5.15.2" ]]; then
echo "FATAL ERROR: Qt version is $QT_VERSION, NOT 5.15.2"
echo "Build CANNOT continue with wrong Qt version"
exit 1
fi
echo "SUCCESS: Qt 5.15.2 verified!"
qmake -v
# Show Qt installation path
QT_INSTALL_PATH=$(dirname $(dirname $(which qmake)))
echo "Qt 5.15.2 installed at: $QT_INSTALL_PATH"
echo "Qt 5.15.2 installation completed successfully (Bluetooth already patched)"
else
echo "FATAL ERROR: No qmake found after installation"
exit 1
fi
# CRITICAL: Generate secret.h from Xcode Cloud environment variables
echo "Generating secret.h from environment variables..."
cd "$CI_PRIMARY_REPOSITORY_PATH/src"
echo "#define STRAVA_SECRET_KEY ${STRAVA_SECRET_KEY}" > secret.h
echo "#define PELOTON_SECRET_KEY ${PELOTON_SECRET_KEY}" >> secret.h
echo "#define SMTP_USERNAME ${SMTP_USERNAME}" >> secret.h
echo "#define SMTP_PASSWORD ${SMTP_PASSWORD}" >> secret.h
echo "#define SMTP_SERVER ${SMTP_SERVER}" >> secret.h
echo "#define INTERVALSICU_CLIENT_ID ${INTERVALSICU_CLIENT_ID}" >> secret.h
echo "#define INTERVALSICU_CLIENT_SECRET ${INTERVALSICU_CLIENT_SECRET}" >> secret.h
echo "secret.h generated successfully"
# Generate cesium-key.js if cesiumkey is provided
if [[ -n "${CESIUMKEY}" ]]; then
echo "Generating cesium-key.js..."
echo "${CESIUMKEY}" > inner_templates/googlemaps/cesium-key.js
echo "cesium-key.js generated successfully"
else
echo "CESIUMKEY not provided, skipping cesium-key.js generation"
fi
cd "$CI_PRIMARY_REPOSITORY_PATH"
# CRITICAL FIX: Disable legacy build locations to enable Swift Package support
# This must be done BEFORE xcodebuild -resolvePackageDependencies is called
echo "Configuring Xcode project to disable legacy build locations..."
cd "$CI_PRIMARY_REPOSITORY_PATH/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug"
# Create xcshareddata directory if it doesn't exist
mkdir -p qdomyoszwift.xcodeproj/project.xcworkspace/xcshareddata
# Create WorkspaceSettings.xcsettings to disable legacy build locations
cat > qdomyoszwift.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildSystemType</key>
<string>Latest</string>
<key>BuildLocationStyle</key>
<string>UseAppPreferences</string>
</dict>
</plist>
EOF
# Create IDEWorkspaceChecks.plist
cat > qdomyoszwift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
EOF
echo "Workspace settings created - modern build system enabled"
# Remove SYMROOT from project.pbxproj to disable legacy build locations
if [[ -f "qdomyoszwift.xcodeproj/project.pbxproj" ]]; then
echo "Removing SYMROOT settings from project.pbxproj..."
sed -i '' '/SYMROOT = /d' qdomyoszwift.xcodeproj/project.pbxproj
sed -i '' '/OBJROOT = /d' qdomyoszwift.xcodeproj/project.pbxproj
# Fix all absolute paths: replace local machine path with Xcode Cloud path
echo "Fixing absolute paths for Xcode Cloud..."
sed -i '' 's|/Users/cagnulein/qdomyos-zwift/|/Volumes/workspace/repository/|g' qdomyoszwift.xcodeproj/project.pbxproj
echo "SYMROOT removed and paths fixed - legacy build locations disabled"
else
echo "WARNING: project.pbxproj not found"
fi
cd "$CI_PRIMARY_REPOSITORY_PATH"
echo "Post-clone setup completed successfully - Qt 5.15.2 EXACTLY installed"

View File

@@ -1,354 +0,0 @@
#!/bin/bash
set -e
echo "=== QDomyos-Zwift CI Pre-Xcodebuild Script ==="
echo "Running qmake to generate Xcode project with MOC files"
# CRITICAL: Load Qt environment from persistent file
echo "Loading Qt environment from ci_post_clone.sh..."
if [[ -f "/tmp/qt_env.sh" ]]; then
echo "Found Qt environment file, loading..."
source /tmp/qt_env.sh
echo "Qt environment loaded from persistent file"
echo "QT_DIR: $QT_DIR"
echo "PATH: $PATH"
else
echo "WARNING: No Qt environment file found, trying to find Qt anyway..."
fi
# Find Qt installation (should be 5.15.2 from post_clone script)
if command -v qmake &> /dev/null; then
QT_VERSION=$(qmake -v | grep -o "5\.[0-9]*\.[0-9]*" | head -1)
if [[ "$QT_VERSION" != "5.15.2" ]]; then
echo "FATAL ERROR: Qt version is $QT_VERSION, expected 5.15.2"
exit 1
fi
echo "Using Qt 5.15.2 - CORRECT!"
echo "qmake location: $(which qmake)"
else
echo "FATAL ERROR: qmake not found"
echo "Current PATH: $PATH"
echo "Listing /tmp for debugging:"
ls -la /tmp/ | grep -i qt || echo "No Qt directories in /tmp"
exit 1
fi
# Change to project root directory
cd ../..
# CRITICAL: Save absolute path to project root for later use
PROJECT_ROOT="$(pwd)"
export PROJECT_ROOT
echo "Project root saved: $PROJECT_ROOT"
# Verify we're in the correct directory
if [[ ! -f "qdomyos-zwift.pro" ]]; then
echo "ERROR: qdomyos-zwift.pro not found. Are we in the right directory?"
pwd
ls -la
exit 1
fi
echo "Current directory: $(pwd)"
echo "Running qmake for iOS Debug build..."
# Run qmake to generate Xcode project and Makefiles
# Use release config since precompiled Qt doesn't have debug libs
# Force iphoneos SDK for device builds (not simulator)
export QMAKE_XCODE_DEVELOPER_PATH="/Applications/Xcode.app/Contents/Developer"
export QMAKE_IOS_DEPLOYMENT_TARGET=12.0
qmake -spec macx-ios-clang CONFIG+=release CONFIG+=device CONFIG-=simulator CONFIG+=iphoneos "QMAKE_APPLE_DEVICE_ARCHS=arm64"
echo "qmake completed successfully"
# CRITICAL: Debug Qt installation before make
echo "Debugging Qt installation before make..."
echo "Checking Qt include directories:"
ls -la /tmp/Qt-5.15.2/ios/include/ 2>/dev/null || echo "No /tmp/Qt-5.15.2/ios/include/"
ls -la /private/tmp/Qt-5.15.2/ios/include/ 2>/dev/null || echo "No /private/tmp/Qt-5.15.2/ios/include/"
echo "Checking for QDebug specifically:"
find /tmp/Qt-5.15.2/ios/include/ -name "*QDebug*" 2>/dev/null || echo "QDebug not found in /tmp/"
find /private/tmp/Qt-5.15.2/ios/include/ -name "*QDebug*" 2>/dev/null || echo "QDebug not found in /private/tmp/"
echo "Checking QtCore include directory:"
ls -la /tmp/Qt-5.15.2/ios/include/QtCore/ 2>/dev/null || echo "No QtCore in /tmp/"
ls -la /private/tmp/Qt-5.15.2/ios/include/QtCore/ 2>/dev/null || echo "No QtCore in /private/tmp/"
# Setup build cache for faster compilation
BUILD_CACHE_DIR="$HOME/Library/Caches/XcodeCloud/QDomyos-Zwift-Build"
mkdir -p "$BUILD_CACHE_DIR"
# Check if we have cached object files
if [[ -d "$BUILD_CACHE_DIR/objects" && -f "$BUILD_CACHE_DIR/build_hash.txt" ]]; then
CURRENT_HASH=$(find "$PROJECT_ROOT/src" -name "*.cpp" -o -name "*.h" -o -name "*.mm" | sort | xargs cat | shasum -a 256 | cut -d' ' -f1)
CACHED_HASH=$(cat "$BUILD_CACHE_DIR/build_hash.txt" 2>/dev/null || echo "none")
if [[ "$CURRENT_HASH" == "$CACHED_HASH" ]]; then
echo "Source files unchanged, restoring build cache..."
if cp -r "$BUILD_CACHE_DIR/objects/"* . 2>/dev/null; then
echo "Build cache restored successfully"
else
echo "Cache restoration failed, will build from scratch"
fi
else
echo "Source files changed, cache invalid"
rm -rf "$BUILD_CACHE_DIR/objects" "$BUILD_CACHE_DIR/build_hash.txt"
fi
fi
# CRITICAL: Create fake xcodebuild BEFORE make to prevent build failures
# During make, qmake will try to call xcodebuild which will fail due to code signing
# We create a fake xcodebuild that just returns success
echo "Creating fake xcodebuild to skip Xcode build during make..."
mkdir -p /tmp/fake_xcode
cat > /tmp/fake_xcode/xcodebuild << 'XCODE_EOF'
#!/bin/bash
echo "Skipping xcodebuild during make - will use correct project later"
exit 0
XCODE_EOF
chmod +x /tmp/fake_xcode/xcodebuild
# Prepend fake xcodebuild to PATH so it's found first
export PATH="/tmp/fake_xcode:$PATH"
echo "Fake xcodebuild created and added to PATH"
which xcodebuild
# CRITICAL: Run make to compile Qt project and generate MOC files
echo "Running make to compile Qt project and generate MOC files..."
# Use parallel compilation for faster builds
make -j$(sysctl -n hw.ncpu)
echo "make completed successfully - MOC files generated"
# Remove fake xcodebuild from PATH
export PATH="${PATH#/tmp/fake_xcode:}"
echo "Fake xcodebuild removed from PATH"
# Cache the build results for next time
echo "Caching build results..."
mkdir -p "$BUILD_CACHE_DIR/objects"
# Cache compiled object files and MOC files
find . -name "*.o" -o -name "moc_*.cpp" -o -name "moc_*.h" | while read file; do
cp "$file" "$BUILD_CACHE_DIR/objects/" 2>/dev/null || echo "Could not cache $file"
done
# Store hash of source files for cache validation
CURRENT_HASH=$(find "$PROJECT_ROOT/src" -name "*.cpp" -o -name "*.h" -o -name "*.mm" | sort | xargs cat | shasum -a 256 | cut -d' ' -f1)
echo "$CURRENT_HASH" > "$BUILD_CACHE_DIR/build_hash.txt"
echo "Build cache updated"
# NOW restore Xcode project and fix qmake corruption AFTER make
echo "Restoring Xcode project from git AFTER make..."
echo "qmake regenerates src/qdomyoszwift.xcodeproj without proper code signing"
# Return to project root for git operations (use absolute path)
cd "$PROJECT_ROOT"
echo "Back to project root: $(pwd)"
# Restore the build directory project (has WatchOS and proper code signing)
git checkout -- build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/
echo "Build directory Xcode project restored from git"
# CRITICAL: Verify Qt labs calendar library exists
echo "Verifying Qt labs calendar library..."
if [[ -f "/tmp/Qt-5.15.2/ios/qml/Qt/labs/calendar/libqtlabscalendarplugin.a" ]]; then
echo "SUCCESS: libqtlabscalendarplugin.a found"
ls -lh /tmp/Qt-5.15.2/ios/qml/Qt/labs/calendar/libqtlabscalendarplugin.a
else
echo "ERROR: libqtlabscalendarplugin.a NOT FOUND"
echo "Searching for calendar files..."
find /tmp/Qt-5.15.2 -name "*calendar*" 2>/dev/null || echo "No calendar files found"
fi
# CRITICAL: Fix ALL paths in Xcode project for Xcode Cloud compatibility
# The project has absolute paths from local development that need to be converted
echo "Fixing all paths in Xcode project for Xcode Cloud..."
cd "$PROJECT_ROOT/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug"
if [[ -f "qdomyoszwift.xcodeproj/project.pbxproj" ]]; then
echo "Converting local development paths to Xcode Cloud paths..."
# Fix all paths in correct order (specific to general)
# 1. Fix Qt library paths (most specific)
sed -i '' 's|/Users/cagnulein/Qt/5\.15\.2/ios/|/tmp/Qt-5.15.2/ios/|g' qdomyoszwift.xcodeproj/project.pbxproj
sed -i '' 's|../../Qt/5\.15\.2/ios/|/tmp/Qt-5.15.2/ios/|g' qdomyoszwift.xcodeproj/project.pbxproj
sed -i '' 's|../Qt/5\.15\.2/ios/|/tmp/Qt-5.15.2/ios/|g' qdomyoszwift.xcodeproj/project.pbxproj
# 2. Fix source file paths to relative (must be before general fix)
sed -i '' 's|/Users/cagnulein/qdomyos-zwift/src/|../src/|g' qdomyoszwift.xcodeproj/project.pbxproj
# 3. Fix all other absolute paths with general replacement
sed -i '' 's|/Users/cagnulein/qdomyos-zwift/|/Volumes/workspace/repository/|g' qdomyoszwift.xcodeproj/project.pbxproj
# 4. Fix sourceTree for relative src paths (must be <group> not <absolute>)
sed -i '' 's|path = "\.\./src/\([^"]*\)"; sourceTree = "<absolute>";|path = "../src/\1"; sourceTree = "<group>";|g' qdomyoszwift.xcodeproj/project.pbxproj
echo "Fixed all paths in project file"
# CRITICAL: Change scheme to Release configuration
# The scheme is committed with Debug configuration but we need Release for Xcode Cloud
echo "Changing scheme to Release configuration..."
if [[ -f "qdomyoszwift.xcodeproj/xcshareddata/xcschemes/qdomyoszwift.xcscheme" ]]; then
# Change TestAction from Debug to Release
sed -i '' 's|<TestAction[^>]*buildConfiguration = "Debug"|<TestAction buildConfiguration = "Release"|g' qdomyoszwift.xcodeproj/xcshareddata/xcschemes/qdomyoszwift.xcscheme
# Change LaunchAction from Debug to Release
sed -i '' 's|<LaunchAction[^>]*buildConfiguration = "Debug"|<LaunchAction buildConfiguration = "Release"|g' qdomyoszwift.xcodeproj/xcshareddata/xcschemes/qdomyoszwift.xcscheme
# Change AnalyzeAction from Debug to Release
sed -i '' 's|<AnalyzeAction[^>]*buildConfiguration = "Debug"|<AnalyzeAction buildConfiguration = "Release"|g' qdomyoszwift.xcodeproj/xcshareddata/xcschemes/qdomyoszwift.xcscheme
echo "Scheme changed to Release configuration"
else
echo "WARNING: Scheme file not found"
fi
# CRITICAL: Remove _debug suffix from Qt libraries
# The Qt package only contains release libraries, not debug versions
# Replace all lib*_debug.a references with lib*.a (release versions)
echo "Replacing debug Qt libraries with release versions..."
sed -i '' 's|lib\([a-zA-Z0-9_]*\)_debug\.a|lib\1.a|g' qdomyoszwift.xcodeproj/project.pbxproj
sed -i '' 's|-l\([a-zA-Z0-9_]*\)_debug|-l\1|g' qdomyoszwift.xcodeproj/project.pbxproj
echo "Replaced all _debug library references with release versions"
# Add ALL necessary Qt library search paths
# qmake generates these but they might be missing from the committed project
echo "Adding all Qt library search paths..."
sed -i '' 's|\(LIBRARY_SEARCH_PATHS = (\)|\1\n\t\t\t\t/tmp/Qt-5.15.2/ios/qml/Qt/labs/calendar,\n\t\t\t\t/tmp/Qt-5.15.2/ios/qml/Qt/labs/platform,\n\t\t\t\t/tmp/Qt-5.15.2/ios/qml/QtCharts,\n\t\t\t\t/tmp/Qt-5.15.2/ios/qml/QtWebView,\n\t\t\t\t/tmp/Qt-5.15.2/ios/qml/QtPositioning,\n\t\t\t\t/tmp/Qt-5.15.2/ios/qml/QtLocation,\n\t\t\t\t/tmp/Qt-5.15.2/ios/qml/QtMultimedia,\n\t\t\t\t/tmp/Qt-5.15.2/ios/plugins/platforms,\n\t\t\t\t/tmp/Qt-5.15.2/ios/plugins/webview,\n\t\t\t\t/tmp/Qt-5.15.2/ios/plugins/texttospeech,\n\t\t\t\t/tmp/Qt-5.15.2/ios/plugins/geoservices,\n\t\t\t\t/tmp/Qt-5.15.2/ios/plugins/sqldrivers,\n\t\t\t\t/tmp/Qt-5.15.2/ios/plugins/mediaservice,\n\t\t\t\t/tmp/Qt-5.15.2/ios/plugins/playlistformats,\n\t\t\t\t/tmp/Qt-5.15.2/ios/plugins/audio,|g' qdomyoszwift.xcodeproj/project.pbxproj
echo "Added all necessary Qt library search paths"
# Verify the fix
grep -c "libqtlabscalendarplugin.a" qdomyoszwift.xcodeproj/project.pbxproj && echo "qtlabscalendarplugin references found"
grep -c "labs/calendar" qdomyoszwift.xcodeproj/project.pbxproj && echo "labs/calendar path references found"
else
echo "ERROR: project.pbxproj not found"
exit 1
fi
cd "$PROJECT_ROOT"
# CRITICAL: Copy ALL generated files from src/ to build directory AFTER git restore
# qmake/make generates many files (moc_*.cpp, qrc_*.cpp, *.o, *.json, qmltyperegistrations, etc.) in src/
# but Xcode project expects them in build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/
# This must happen AFTER git checkout to avoid wiping out the copied files
echo "Copying ALL Qt-generated files from src/ to build directory..."
cd "$PROJECT_ROOT/src"
# Copy all generated files (cpp, o, json, a) but exclude directories
echo "Looking for generated files in: $(pwd)"
find . -maxdepth 1 -type f \( -name "moc_*.cpp" -o -name "moc_*.cpp.json" -o -name "qrc_*.cpp" -o -name "*.o" -o -name "*.a" -o -name "*_qmltyperegistrations.*" -o -name "*.qmltypes" -o -name "*_metatypes.json" -o -name "*_plugin_import.cpp" \) -print -exec cp {} "$PROJECT_ROOT/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/" \;
echo "Generated files copied to build directory"
# CRITICAL FIX: Rename qdomyos-zwift_qmltyperegistrations.cpp to qdomyoszwift_qmltyperegistrations.cpp
# qmake generates the file with a hyphen but Xcode project expects it without hyphen
echo "Fixing qmltyperegistrations filename mismatch..."
if [[ -f "$PROJECT_ROOT/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyos-zwift_qmltyperegistrations.cpp" ]]; then
cp "$PROJECT_ROOT/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyos-zwift_qmltyperegistrations.cpp" \
"$PROJECT_ROOT/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift_qmltyperegistrations.cpp"
echo "Renamed qdomyos-zwift_qmltyperegistrations.cpp -> qdomyoszwift_qmltyperegistrations.cpp"
else
echo "WARNING: qdomyos-zwift_qmltyperegistrations.cpp not found in build directory"
fi
# Also handle .o file if it exists
if [[ -f "$PROJECT_ROOT/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyos-zwift_qmltyperegistrations.o" ]]; then
cp "$PROJECT_ROOT/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyos-zwift_qmltyperegistrations.o" \
"$PROJECT_ROOT/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift_qmltyperegistrations.o"
echo "Renamed qdomyos-zwift_qmltyperegistrations.o -> qdomyoszwift_qmltyperegistrations.o"
fi
echo "Verifying qdomyoszwift_qmltyperegistrations.cpp exists:"
ls -la "$PROJECT_ROOT/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift_qmltyperegistrations.cpp" 2>&1
cd "$PROJECT_ROOT"
# CRITICAL FIX: Delete corrupted project in src/ and symlink to the good one
# qmake regenerates src/qdomyoszwift.xcodeproj without code signing during make
# xcodebuild will build from src/, so we symlink to the correct project in build/
echo "Removing corrupted Xcode project from src/ and creating symlink..."
if [[ -d "src/qdomyoszwift.xcodeproj" ]]; then
rm -rf src/qdomyoszwift.xcodeproj
echo "Corrupted project removed from src/"
fi
# Create symlink from src/ to the correct project in build/
ln -s ../build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj src/qdomyoszwift.xcodeproj
echo "Symlink created: src/qdomyoszwift.xcodeproj -> ../build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj"
# Verify symlink
if [[ -L "src/qdomyoszwift.xcodeproj" ]]; then
echo "Symlink verified successfully"
ls -la src/qdomyoszwift.xcodeproj
else
echo "ERROR: Failed to create symlink"
exit 1
fi
echo "Xcode project fix completed - symlink created to correct project with code signing"
# CRITICAL FIX: Disable legacy build locations to enable Swift Package support
# Create workspace settings to force modern build system
echo "Configuring workspace to disable legacy build locations..."
cd build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug
# Create xcshareddata directory if it doesn't exist
mkdir -p qdomyoszwift.xcodeproj/project.xcworkspace/xcshareddata
# Create WorkspaceSettings.xcsettings to disable legacy build locations
cat > qdomyoszwift.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildSystemType</key>
<string>Latest</string>
<key>BuildLocationStyle</key>
<string>UseAppPreferences</string>
</dict>
</plist>
EOF
# Create IDEWorkspaceChecks.plist
cat > qdomyoszwift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
EOF
echo "Workspace settings created - modern build system enabled"
if [[ -f "qdomyoszwift.xcodeproj/project.pbxproj" ]]; then
echo "Removing SYMROOT settings from project.pbxproj..."
# Remove all SYMROOT lines completely (they cause the legacy build locations error)
sed -i '' '/SYMROOT = /d' qdomyoszwift.xcodeproj/project.pbxproj
# Also remove OBJROOT if present
sed -i '' '/OBJROOT = /d' qdomyoszwift.xcodeproj/project.pbxproj
# Ensure new build system is enabled
sed -i '' 's/UseNewBuildSystem = NO/UseNewBuildSystem = YES/g' qdomyoszwift.xcodeproj/project.pbxproj || echo "New build system already enabled"
echo "Legacy build locations disabled - Swift packages now supported"
else
echo "ERROR: Xcode project not found"
exit 1
fi
# Verify the Xcode project exists and is properly configured
if [[ -f "qdomyoszwift.xcodeproj/project.pbxproj" ]]; then
echo "Xcode project found and configured for Xcode Cloud"
echo "Project size: $(du -sh qdomyoszwift.xcodeproj)"
else
echo "ERROR: Xcode project not found after qmake"
exit 1
fi
echo "Pre-xcodebuild setup completed successfully"

View File

@@ -2,4 +2,3 @@
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "swiftDebug.h"

View File

@@ -53,17 +53,12 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_accessibility_support_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_bluetooth.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_bluetooth_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_bodymovin_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_bootstrap_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_charts.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_charts_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_clipboard_support_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_concurrent.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_concurrent_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_core.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_core_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_datavisualization.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_datavisualization_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_devicediscovery_support_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_edid_support_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_eventdispatcher_support_private.pri \
@@ -76,9 +71,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_gui_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_help.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_help_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules-inst/qt_lib_httpserver.pri \
../../Qt/5.15.2/ios/mkspecs/modules-inst/qt_lib_httpserver_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_httpserver.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_location.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_location_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_macextras.pri \
@@ -103,8 +95,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_positioning_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_positioningquick.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_positioningquick_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_purchasing.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_purchasing_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qml.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qml_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qmldebug_private.pri \
@@ -117,16 +107,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qmlworkerscript_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qtmultimediaquicktools_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3d.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3d_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3dassetimport.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3dassetimport_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3drender.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3drender_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3druntimerender.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3druntimerender_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3dutils.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3dutils_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quickcontrols2.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quickcontrols2_private.pri \
@@ -140,21 +120,12 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_remoteobjects_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_repparser.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_repparser_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_script.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_script_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_scripttools.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_scripttools_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_scxml.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_scxml_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sensors.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sensors_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_serialbus.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_serialbus_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sql.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sql_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules-inst/qt_lib_sslserver.pri \
../../Qt/5.15.2/ios/mkspecs/modules-inst/qt_lib_sslserver_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sslserver.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_svg.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_svg_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_testlib.pri \
@@ -165,8 +136,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_uiplugin.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_uitools.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_uitools_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_virtualkeyboard.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_virtualkeyboard_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_webchannel.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_webchannel_private.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_websockets.pri \
@@ -225,26 +194,15 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtiff.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtmedia_audioengine.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtmultimedia_m3u.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtpassthrucanbus.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtpeakcanbus.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtposition_cl.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtposition_positionpoll.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtsensorgestures_plugin.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtsensorgestures_shakeplugin.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtsensors_generic.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtsensors_ios.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qttinycanbus.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtuiotouchplugin.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualcanbus.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_hangul.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_openwnn.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_pinyin.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_tcime.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_thai.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboardplugin.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtwebview_darwin.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qwbmp.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qwebgl.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qwebp.pri \
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_scene2d.pri \
../../Qt/5.15.2/ios/mkspecs/features/qt_functions.prf \
@@ -261,9 +219,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/features/default_pre.prf \
../../Qt/5.15.2/ios/mkspecs/features/mac/default_pre.prf \
../../Qt/5.15.2/ios/mkspecs/features/uikit/default_pre.prf \
../defaults.pri \
../src/purchasing/purchasing.pri \
../src/qdomyos-zwift.pri \
../../Qt/5.15.2/ios/mkspecs/features/resolve_config.prf \
../../Qt/5.15.2/ios/mkspecs/features/uikit/resolve_config.prf \
../../Qt/5.15.2/ios/mkspecs/features/default_post.prf \
@@ -271,9 +226,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/features/uikit/default_post.prf \
../../Qt/5.15.2/ios/mkspecs/macx-ios-clang/features/default_post.prf \
../../Qt/5.15.2/ios/mkspecs/features/mac/objective_c.prf \
../../Qt/5.15.2/ios/mkspecs/features/qmltypes.prf \
../../Qt/5.15.2/ios/mkspecs/features/metatypes.prf \
../../Qt/5.15.2/ios/mkspecs/features/ltcg.prf \
../../Qt/5.15.2/ios/mkspecs/features/qml_debug.prf \
../../Qt/5.15.2/ios/mkspecs/features/mac/mac.prf \
../../Qt/5.15.2/ios/mkspecs/features/uikit/bitcode.prf \
@@ -312,18 +264,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/lib/libqtharfbuzz_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5Core_debug.prl \
../../Qt/5.15.2/ios/lib/libqtpcre2_debug.prl \
../../Qt/5.15.2/ios/plugins/mediaservice/libqavfmediaplayer_debug.prl \
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_esri_debug.prl \
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_itemsoverlay_debug.prl \
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_mapbox_debug.prl \
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_mapboxgl_debug.prl \
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_nokia_debug.prl \
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_osm_debug.prl \
../../Qt/5.15.2/ios/plugins/webview/libqtwebview_darwin_debug.prl \
../../Qt/5.15.2/ios/plugins/mediaservice/libqavfcamera_debug.prl \
../../Qt/5.15.2/ios/plugins/mediaservice/libqtmedia_audioengine_debug.prl \
../../Qt/5.15.2/ios/plugins/audio/libqtaudio_coreaudio_debug.prl \
../../Qt/5.15.2/ios/plugins/playlistformats/libqtmultimedia_m3u_debug.prl \
../../Qt/5.15.2/ios/plugins/imageformats/libqgif_debug.prl \
../../Qt/5.15.2/ios/plugins/imageformats/libqicns_debug.prl \
../../Qt/5.15.2/ios/plugins/imageformats/libqico_debug.prl \
@@ -348,51 +288,32 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/plugins/qmltooling/libqmldbg_server_debug.prl \
../../Qt/5.15.2/ios/plugins/qmltooling/libqmldbg_tcp_debug.prl \
../../Qt/5.15.2/ios/plugins/bearer/libqgenericbearer_debug.prl \
../../Qt/5.15.2/ios/plugins/texttospeech/libqtexttospeech_speechios_debug.prl \
../../Qt/5.15.2/ios/plugins/sqldrivers/libqsqlite_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5HttpServer_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5SslServer_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5Charts_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5Widgets_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5Location_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5PositioningQuick_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5QuickControls2_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5Quick_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5Multimedia_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5WebView_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5Bluetooth_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5Xml_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5Positioning_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5QmlModels_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5Qml_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5NetworkAuth_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5WebSockets_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5Network_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5TextToSpeech_debug.prl \
../../Qt/5.15.2/ios/lib/libQt5Concurrent_debug.prl \
../../Qt/5.15.2/ios/qml/QtQuick.2/libqtquick2plugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtQuick/Layouts/libqquicklayoutsplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/libqtquickcontrols2plugin_debug.prl \
../../Qt/5.15.2/ios/qml/Qt/labs/settings/libqmlsettingsplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Material/libqtquickcontrols2materialstyleplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtGraphicalEffects/libqtgraphicaleffectsplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtQuick/Window.2/libwindowplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtQml/libqmlplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtQuick/Templates.2/libqtquicktemplates2plugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtGraphicalEffects/private/libqtgraphicaleffectsprivate_debug.prl \
../../Qt/5.15.2/ios/qml/QtQml/Models.2/libmodelsplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtQml/WorkerScript.2/libworkerscriptplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtQuick/Window.2/libwindowplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Material/libqtquickcontrols2materialstyleplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtWebView/libdeclarative_webview_debug.prl \
../../Qt/5.15.2/ios/qml/QtCharts/libqtchartsqml2_debug.prl \
../../Qt/5.15.2/ios/qml/Qt/labs/folderlistmodel/libqmlfolderlistmodelplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtQuick/Dialogs/libdialogplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtPositioning/libdeclarative_positioning_debug.prl \
../../Qt/5.15.2/ios/qml/QtLocation/libdeclarative_location_debug.prl \
../../Qt/5.15.2/ios/qml/Qt/labs/folderlistmodel/libqmlfolderlistmodelplugin_debug.prl \
../../Qt/5.15.2/ios/qml/Qt/labs/settings/libqmlsettingsplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtQuick/Dialogs/Private/libdialogsprivateplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtQuick/Controls/libqtquickcontrolsplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtQuick/PrivateWidgets/libwidgetsplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtGraphicalEffects/libqtgraphicaleffectsplugin_debug.prl \
../../Qt/5.15.2/ios/qml/Qt/labs/platform/libqtlabsplatformplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtMultimedia/libdeclarative_multimedia_debug.prl \
../../Qt/5.15.2/ios/qml/QtGraphicalEffects/private/libqtgraphicaleffectsprivate_debug.prl \
../../Qt/5.15.2/ios/qml/QtQuick/Layouts/libqquicklayoutsplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Fusion/libqtquickcontrols2fusionstyleplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Universal/libqtquickcontrols2universalstyleplugin_debug.prl \
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Imagine/libqtquickcontrols2imaginestyleplugin_debug.prl
@@ -440,17 +361,12 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_accessibility_support_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_bluetooth.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_bluetooth_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_bodymovin_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_bootstrap_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_charts.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_charts_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_clipboard_support_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_concurrent.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_concurrent_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_core.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_core_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_datavisualization.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_datavisualization_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_devicediscovery_support_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_edid_support_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_eventdispatcher_support_private.pri:
@@ -463,9 +379,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_gui_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_help.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_help_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules-inst/qt_lib_httpserver.pri:
../../Qt/5.15.2/ios/mkspecs/modules-inst/qt_lib_httpserver_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_httpserver.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_location.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_location_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_macextras.pri:
@@ -490,8 +403,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_positioning_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_positioningquick.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_positioningquick_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_purchasing.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_purchasing_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qml.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qml_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qmldebug_private.pri:
@@ -504,16 +415,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qmlworkerscript_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qtmultimediaquicktools_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3d.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3d_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3dassetimport.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3dassetimport_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3drender.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3drender_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3druntimerender.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3druntimerender_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3dutils.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3dutils_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quickcontrols2.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quickcontrols2_private.pri:
@@ -527,21 +428,12 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_remoteobjects_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_repparser.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_repparser_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_script.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_script_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_scripttools.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_scripttools_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_scxml.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_scxml_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sensors.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sensors_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_serialbus.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_serialbus_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sql.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sql_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules-inst/qt_lib_sslserver.pri:
../../Qt/5.15.2/ios/mkspecs/modules-inst/qt_lib_sslserver_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sslserver.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_svg.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_svg_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_testlib.pri:
@@ -552,8 +444,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_uiplugin.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_uitools.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_uitools_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_virtualkeyboard.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_virtualkeyboard_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_webchannel.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_webchannel_private.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_websockets.pri:
@@ -612,26 +502,15 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtiff.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtmedia_audioengine.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtmultimedia_m3u.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtpassthrucanbus.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtpeakcanbus.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtposition_cl.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtposition_positionpoll.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtsensorgestures_plugin.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtsensorgestures_shakeplugin.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtsensors_generic.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtsensors_ios.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qttinycanbus.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtuiotouchplugin.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualcanbus.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_hangul.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_openwnn.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_pinyin.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_tcime.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_thai.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboardplugin.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtwebview_darwin.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qwbmp.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qwebgl.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qwebp.pri:
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_scene2d.pri:
../../Qt/5.15.2/ios/mkspecs/features/qt_functions.prf:
@@ -648,9 +527,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/features/default_pre.prf:
../../Qt/5.15.2/ios/mkspecs/features/mac/default_pre.prf:
../../Qt/5.15.2/ios/mkspecs/features/uikit/default_pre.prf:
../defaults.pri:
../src/purchasing/purchasing.pri:
../src/qdomyos-zwift.pri:
../../Qt/5.15.2/ios/mkspecs/features/resolve_config.prf:
../../Qt/5.15.2/ios/mkspecs/features/uikit/resolve_config.prf:
../../Qt/5.15.2/ios/mkspecs/features/default_post.prf:
@@ -658,9 +534,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/mkspecs/features/uikit/default_post.prf:
../../Qt/5.15.2/ios/mkspecs/macx-ios-clang/features/default_post.prf:
../../Qt/5.15.2/ios/mkspecs/features/mac/objective_c.prf:
../../Qt/5.15.2/ios/mkspecs/features/qmltypes.prf:
../../Qt/5.15.2/ios/mkspecs/features/metatypes.prf:
../../Qt/5.15.2/ios/mkspecs/features/ltcg.prf:
../../Qt/5.15.2/ios/mkspecs/features/qml_debug.prf:
../../Qt/5.15.2/ios/mkspecs/features/mac/mac.prf:
../../Qt/5.15.2/ios/mkspecs/features/uikit/bitcode.prf:
@@ -699,18 +572,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/lib/libqtharfbuzz_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5Core_debug.prl:
../../Qt/5.15.2/ios/lib/libqtpcre2_debug.prl:
../../Qt/5.15.2/ios/plugins/mediaservice/libqavfmediaplayer_debug.prl:
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_esri_debug.prl:
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_itemsoverlay_debug.prl:
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_mapbox_debug.prl:
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_mapboxgl_debug.prl:
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_nokia_debug.prl:
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_osm_debug.prl:
../../Qt/5.15.2/ios/plugins/webview/libqtwebview_darwin_debug.prl:
../../Qt/5.15.2/ios/plugins/mediaservice/libqavfcamera_debug.prl:
../../Qt/5.15.2/ios/plugins/mediaservice/libqtmedia_audioengine_debug.prl:
../../Qt/5.15.2/ios/plugins/audio/libqtaudio_coreaudio_debug.prl:
../../Qt/5.15.2/ios/plugins/playlistformats/libqtmultimedia_m3u_debug.prl:
../../Qt/5.15.2/ios/plugins/imageformats/libqgif_debug.prl:
../../Qt/5.15.2/ios/plugins/imageformats/libqicns_debug.prl:
../../Qt/5.15.2/ios/plugins/imageformats/libqico_debug.prl:
@@ -735,51 +596,32 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
../../Qt/5.15.2/ios/plugins/qmltooling/libqmldbg_server_debug.prl:
../../Qt/5.15.2/ios/plugins/qmltooling/libqmldbg_tcp_debug.prl:
../../Qt/5.15.2/ios/plugins/bearer/libqgenericbearer_debug.prl:
../../Qt/5.15.2/ios/plugins/texttospeech/libqtexttospeech_speechios_debug.prl:
../../Qt/5.15.2/ios/plugins/sqldrivers/libqsqlite_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5HttpServer_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5SslServer_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5Charts_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5Widgets_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5Location_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5PositioningQuick_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5QuickControls2_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5Quick_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5Multimedia_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5WebView_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5Bluetooth_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5Xml_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5Positioning_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5QmlModels_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5Qml_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5NetworkAuth_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5WebSockets_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5Network_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5TextToSpeech_debug.prl:
../../Qt/5.15.2/ios/lib/libQt5Concurrent_debug.prl:
../../Qt/5.15.2/ios/qml/QtQuick.2/libqtquick2plugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtQuick/Layouts/libqquicklayoutsplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/libqtquickcontrols2plugin_debug.prl:
../../Qt/5.15.2/ios/qml/Qt/labs/settings/libqmlsettingsplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Material/libqtquickcontrols2materialstyleplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtGraphicalEffects/libqtgraphicaleffectsplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtQuick/Window.2/libwindowplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtQml/libqmlplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtQuick/Templates.2/libqtquicktemplates2plugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtGraphicalEffects/private/libqtgraphicaleffectsprivate_debug.prl:
../../Qt/5.15.2/ios/qml/QtQml/Models.2/libmodelsplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtQml/WorkerScript.2/libworkerscriptplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtQuick/Window.2/libwindowplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Material/libqtquickcontrols2materialstyleplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtWebView/libdeclarative_webview_debug.prl:
../../Qt/5.15.2/ios/qml/QtCharts/libqtchartsqml2_debug.prl:
../../Qt/5.15.2/ios/qml/Qt/labs/folderlistmodel/libqmlfolderlistmodelplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtQuick/Dialogs/libdialogplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtPositioning/libdeclarative_positioning_debug.prl:
../../Qt/5.15.2/ios/qml/QtLocation/libdeclarative_location_debug.prl:
../../Qt/5.15.2/ios/qml/Qt/labs/folderlistmodel/libqmlfolderlistmodelplugin_debug.prl:
../../Qt/5.15.2/ios/qml/Qt/labs/settings/libqmlsettingsplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtQuick/Dialogs/Private/libdialogsprivateplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtQuick/Controls/libqtquickcontrolsplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtQuick/PrivateWidgets/libwidgetsplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtGraphicalEffects/libqtgraphicaleffectsplugin_debug.prl:
../../Qt/5.15.2/ios/qml/Qt/labs/platform/libqtlabsplatformplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtMultimedia/libdeclarative_multimedia_debug.prl:
../../Qt/5.15.2/ios/qml/QtGraphicalEffects/private/libqtgraphicaleffectsprivate_debug.prl:
../../Qt/5.15.2/ios/qml/QtQuick/Layouts/libqquicklayoutsplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Fusion/libqtquickcontrols2fusionstyleplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Universal/libqtquickcontrols2universalstyleplugin_debug.prl:
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Imagine/libqtquickcontrols2imaginestyleplugin_debug.prl:

View File

@@ -1,16 +0,0 @@
{
"object": {
"pins": [
{
"package": "swift-protobuf",
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
"state": {
"branch": null,
"revision": "65e8f29b2d63c4e38e736b25c27b83e012159be8",
"version": "1.25.2"
}
}
]
},
"version": 1
}

View File

@@ -54,10 +54,8 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/qdomyoszwift">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "876E4E102594747F00BD5714"
@@ -65,7 +63,7 @@
BlueprintName = "watchkit"
ReferencedContainer = "container:qdomyoszwift.xcodeproj">
</BuildableReference>
</RemoteRunnable>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -73,10 +71,8 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/qdomyoszwift">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "876E4E102594747F00BD5714"
@@ -84,14 +80,7 @@
BlueprintName = "watchkit"
ReferencedContainer = "container:qdomyoszwift.xcodeproj">
</BuildableReference>
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableName = "watchkit.app"
BlueprintName = "watchkit"
ReferencedContainer = "container:qdomyoszwift.xcodeproj">
</BuildableReference>
</MacroExpansion>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@@ -193,38 +193,6 @@
endingLineNumber = "57"
landmarkName = "BLEPeripheralManager"
landmarkType = "3">
<Locations>
<Location
uuid = "16D24B27-D0FB-4EC3-BAE8-56101FE7949B - 1c798ec95ff8d4b7"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "qdomyoszwift.BLEPeripheralManager.crankRevolutions.modify : Swift.Optional&lt;Swift.UInt16&gt;"
moduleName = "qdomyoszwift"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/cagnulein/qdomyos-zwift/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/%3Ccompiler-generated%3E"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "0"
endingLineNumber = "0"
offsetFromSymbolStart = "16">
</Location>
<Location
uuid = "16D24B27-D0FB-4EC3-BAE8-56101FE7949B - 5ebbef0dc9913f07"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "qdomyoszwift.BLEPeripheralManager.init() -&gt; qdomyoszwift.BLEPeripheralManager"
moduleName = "qdomyoszwift"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/cagnulein/qdomyos-zwift/src/ios/BLEPeripheralManager.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "57"
endingLineNumber = "57"
offsetFromSymbolStart = "132">
</Location>
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
@@ -367,7 +335,7 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "38"
endingLineNumber = "38"
landmarkName = "lockscreen::stepCadence()"
landmarkName = "lockscreen::virtualbike_setHeartRate(heartRate)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
@@ -375,7 +343,7 @@
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "FE5697FF-F44C-43C2-A98D-C400EE56F047"
shouldBeEnabled = "No"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "../src/ios/lockscreen.mm"
@@ -383,8 +351,8 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "44"
endingLineNumber = "44"
landmarkName = "unknown"
landmarkType = "0">
landmarkName = "lockscreen::virtualbike_setCadence(crankRevolutions, lastCrankEventTime)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
@@ -399,7 +367,7 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "37"
endingLineNumber = "37"
landmarkName = "lockscreen::stepCadence()"
landmarkName = "lockscreen::virtualbike_setHeartRate(heartRate)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
@@ -407,7 +375,7 @@
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "3DBE0495-050A-4979-85D4-28B78676F212"
shouldBeEnabled = "No"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "../src/ios/lockscreen.mm"
@@ -415,7 +383,7 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "43"
endingLineNumber = "43"
landmarkName = "lockscreen::setKcal(kcal)"
landmarkName = "lockscreen::virtualbike_setCadence(crankRevolutions, lastCrankEventTime)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
@@ -431,7 +399,7 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "32"
endingLineNumber = "32"
landmarkName = "lockscreen::heartRate()"
landmarkName = "lockscreen::virtualbike_ios()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
@@ -463,7 +431,7 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "35"
endingLineNumber = "35"
offsetFromSymbolStart = "32">
offsetFromSymbolStart = "22">
</Location>
<Location
uuid = "18F27065-9FB2-44A2-99D0-7D41061141A3 - 4daffae51fb2d733"
@@ -478,7 +446,7 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "35"
endingLineNumber = "35"
offsetFromSymbolStart = "36">
offsetFromSymbolStart = "28">
</Location>
</Locations>
</BreakpointContent>

View File

@@ -1,62 +0,0 @@
{
"identifier" : "2816EB89",
"nonRenewingSubscriptions" : [
],
"products" : [
],
"settings" : {
},
"subscriptionGroups" : [
{
"id" : "F012E388",
"localizations" : [
],
"name" : "Swag Bag",
"subscriptions" : [
{
"adHocOffers" : [
],
"codeOffers" : [
],
"displayPrice" : "1.99",
"familyShareable" : false,
"groupNumber" : 1,
"internalID" : "F108BD35",
"introductoryOffer" : null,
"localizations" : [
{
"description" : "Swag Bag",
"displayName" : "Swag Bag",
"locale" : "en_US"
},
{
"description" : "Swag Bag",
"displayName" : "Swag Bag",
"locale" : "en_GB"
},
{
"description" : "Swag Bag",
"displayName" : "Swag Bag",
"locale" : "it"
}
],
"productID" : "org.cagnulein.qdomyoszwift.swagbag",
"recurringSubscriptionPeriod" : "P1M",
"referenceName" : "SwagBag",
"subscriptionGroupID" : "F012E388",
"type" : "RecurringSubscription"
}
]
}
],
"version" : {
"major" : 1,
"minor" : 2
}
}

View File

@@ -1,25 +1,21 @@
{
"images" : [
{
"filename" : "circular38mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"filename" : "circular40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "circular42mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"filename" : "circular44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -1,25 +1,21 @@
{
"images" : [
{
"filename" : "extra-large38mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"filename" : "extra-large40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "extra-large42mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"filename" : "extra-large44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -1,13 +1,11 @@
{
"images" : [
{
"filename" : "graphic-bezel40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "graphic-bezel44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -1,13 +1,11 @@
{
"images" : [
{
"filename" : "graphic-circular40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "graphic-circular44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -1,13 +1,11 @@
{
"images" : [
{
"filename" : "graphic-corner40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "graphic-corner44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -1,25 +1,21 @@
{
"images" : [
{
"filename" : "graphic-extra-large38mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"filename" : "graphic-extra-large40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "graphic-extra-large42mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"filename" : "graphic-extra-large44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -1,13 +1,11 @@
{
"images" : [
{
"filename" : "graphic-large-rectangular40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "graphic-large-rectangular44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -1,25 +1,21 @@
{
"images" : [
{
"filename" : "modular38mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"filename" : "modular40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "modular42mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"filename" : "modular44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -1,25 +1,21 @@
{
"images" : [
{
"filename" : "utility38mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"filename" : "utility40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "utility42mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"filename" : "utility44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -13,59 +13,6 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
// MARK: - Timeline Configuration
private func templateForComplication(complication: CLKComplication) -> CLKComplicationTemplate? {
// Init default output:
var template: CLKComplicationTemplate? = nil
// Graphic Complications are only availably since watchOS 5.0:
if #available(watchOSApplicationExtension 5.0, *) {
// NOTE: Watch faces that support graphic templates are available only on Apple Watch Series 4 or later. So the binary on older devices (e.g. Watch Series 3) will not contain the images.
if complication.family == .graphicCircular {
let imageTemplate = CLKComplicationTemplateGraphicCircularImage()
// Check if asset exists, to prevent crash on non-supported devices:
if let fullColorImage = UIImage(named: "Complication/Graphic Circular") {
let imageProvider = CLKFullColorImageProvider.init(fullColorImage: fullColorImage)
imageTemplate.imageProvider = imageProvider
template = imageTemplate
}
}
else if complication.family == .graphicCorner {
let imageTemplate = CLKComplicationTemplateGraphicCornerCircularImage()
// Check if asset exists, to prevent crash on non-supported devices:
if let fullColorImage = UIImage(named: "Complication/Graphic Corner") {
let imageProvider = CLKFullColorImageProvider.init(fullColorImage: fullColorImage)
imageTemplate.imageProvider = imageProvider
template = imageTemplate
}
}
}
// For all watchOS versions:
if complication.family == .circularSmall {
let imageTemplate = CLKComplicationTemplateCircularSmallSimpleImage()
let imageProvider = CLKImageProvider(onePieceImage: UIImage(named: "Complication/Circular")!)
imageProvider.tintColor = UIColor.blue
imageTemplate.imageProvider = imageProvider
template = imageTemplate
}
else if complication.family == .modularSmall {
let imageTemplate = CLKComplicationTemplateModularSmallSimpleImage()
let imageProvider = CLKImageProvider(onePieceImage: UIImage(named: "Complication/Modular")!)
imageProvider.tintColor = UIColor.blue
imageTemplate.imageProvider = imageProvider
template = imageTemplate
}
else if complication.family == .utilitarianSmall {
let imageTemplate = CLKComplicationTemplateUtilitarianSmallSquare()
let imageProvider = CLKImageProvider(onePieceImage: UIImage(named: "Complication/Utilitarian")!)
imageProvider.tintColor = UIColor.blue
imageTemplate.imageProvider = imageProvider
template = imageTemplate
}
return template
}
func getSupportedTimeTravelDirections(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Void) {
handler([.forward, .backward])
}
@@ -86,9 +33,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
// Call the handler with the current timeline entry
let template = templateForComplication(complication: complication)
let timelineEntry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template!)
handler(timelineEntry)
handler(nil)
}
func getTimelineEntries(for complication: CLKComplication, before date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
@@ -101,15 +46,11 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
handler(nil)
}
func getPlaceholderTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
// This method will be called once per supported complication, and the results will be cached
handler(templateForComplication(complication: complication))
}
// MARK: - Placeholder Templates
func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
// This method will be called once per supported complication, and the results will be cached
handler(templateForComplication(complication: complication))
handler(nil)
}
}

View File

@@ -8,8 +8,6 @@
<string>watchkit Extension</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>NSMotionUsageDescription</key>
<string>access to step cadence in order to show it in the application</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
@@ -24,21 +22,6 @@
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>CLKComplicationPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).ComplicationController</string>
<key>CLKComplicationSupportedFamilies</key>
<array>
<string>CLKComplicationFamilyModularSmall</string>
<string>CLKComplicationFamilyModularLarge</string>
<string>CLKComplicationFamilyUtilitarianSmall</string>
<string>CLKComplicationFamilyUtilitarianSmallFlat</string>
<string>CLKComplicationFamilyUtilitarianLarge</string>
<string>CLKComplicationFamilyCircularSmall</string>
<string>CLKComplicationFamilyExtraLarge</string>
<string>CLKComplicationFamilyGraphicCorner</string>
<string>CLKComplicationFamilyGraphicBezel</string>
<string>CLKComplicationFamilyGraphicCircular</string>
<string>CLKComplicationFamilyGraphicRectangular</string>
<string>CLKComplicationFamilyGraphicExtraLarge</string>
</array>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>

View File

@@ -8,56 +8,26 @@
import WatchKit
import HealthKit
import CoreMotion
class MainController: WKInterfaceController {
@IBOutlet weak var userNameLabel: WKInterfaceLabel!
@IBOutlet weak var stepCountsLabel: WKInterfaceLabel!
@IBOutlet weak var caloriesLabel: WKInterfaceLabel!
@IBOutlet weak var distanceLabel: WKInterfaceLabel!
@IBOutlet weak var heartRateLabel: WKInterfaceLabel!
@IBOutlet weak var startButton: WKInterfaceButton!
@IBOutlet weak var cmbSports: WKInterfacePicker!
static var start: Bool! = false
let pedometer = CMPedometer()
var sport: Int = 0
override func awake(withContext context: Any?) {
super.awake(withContext: context)
let sports: [WKPickerItem] = [WKPickerItem(),WKPickerItem(),WKPickerItem(),WKPickerItem(),WKPickerItem()]
sports[0].title = "Bike"
sports[1].title = "Run"
sports[2].title = "Walk"
sports[3].title = "Elliptical"
sports[4].title = "Rowing"
cmbSports.setItems(sports)
sport = UserDefaults.standard.value(forKey: "sport") as? Int ?? 0
cmbSports.setSelectedItemIndex(sport)
// Configure interface objects here.
print("AWAKE")
}
@IBAction func changeSport(_ value: Int) {
self.sport = value
UserDefaults.standard.set(value, forKey: "sport")
UserDefaults.standard.synchronize()
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
print("WILL ACTIVE")
WorkoutTracking.shared.fetchStepCounts()
if CMPedometer.isStepCountingAvailable() {
pedometer.startUpdates(from: Date()) { pedometerData, error in
guard let pedometerData = pedometerData, error == nil else { return }
self.stepCountsLabel.setText("\(Int(((pedometerData.currentCadence?.doubleValue ?? 0) * 60.0 / 2.0))) STEP CAD.")
WatchKitConnection.stepCadence = Int(((pedometerData.currentCadence?.doubleValue ?? 0) * 60.0 / 2.0))
WatchKitConnection.shared.sendMessage(message: ["stepCadence":
"\(WatchKitConnection.stepCadence)" as AnyObject])
}
}
}
override func didDeactivate() {
@@ -74,7 +44,6 @@ extension MainController {
MainController.start = true
startButton.setTitle("Stop")
WorkoutTracking.authorizeHealthKit()
WorkoutTracking.shared.setSport(sport)
WorkoutTracking.shared.startWorkOut()
WorkoutTracking.shared.delegate = self
@@ -90,7 +59,6 @@ extension MainController {
}
extension MainController: WorkoutTrackingDelegate {
func didReceiveHealthKitDistanceCycling(_ distanceCycling: Double) {
}
@@ -104,25 +72,10 @@ extension MainController: WorkoutTrackingDelegate {
"\(heartRate)" as AnyObject])
WorkoutTracking.distance = WatchKitConnection.distance
WorkoutTracking.kcal = WatchKitConnection.kcal
WorkoutTracking.speed = WatchKitConnection.speed
WorkoutTracking.power = WatchKitConnection.power
WorkoutTracking.cadence = WatchKitConnection.cadence
WorkoutTracking.steps = WatchKitConnection.steps
if Locale.current.measurementSystem != "Metric" {
self.distanceLabel.setText("Distance \(String(format:"%.2f", WorkoutTracking.distance))")
} else {
self.distanceLabel.setText("Distance \(String(format:"%.2f", WorkoutTracking.distance * 1.60934))")
}
self.caloriesLabel.setText("KCal \(Int(WorkoutTracking.kcal))")
//WorkoutTracking.cadenceSteps = pedometer.
}
func didReceiveHealthKitStepCounts(_ stepCounts: Double) {
//stepCountsLabel.setText("\(stepCounts) STEPS")
}
func didReceiveHealthKitStepCadence(_ stepCadence: Double) {
stepCountsLabel.setText("\(stepCounts) STEPS")
}
}
@@ -131,11 +84,3 @@ extension MainController: WatchKitConnectionDelegate {
userNameLabel.setText(userName)
}
}
extension Locale
{
var measurementSystem : String?
{
return (self as NSLocale).object(forKey: NSLocale.Key.measurementSystem) as? String
}
}

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@@ -23,13 +23,6 @@ class WatchKitConnection: NSObject {
static let shared = WatchKitConnection()
public static var distance = 0.0
public static var kcal = 0.0
public static var totalKcal = 0.0
public static var stepCadence = 0
public static var speed = 0.0
public static var cadence = 0.0
public static var power = 0.0
public static var steps = 0
public static var elevationGain = 0.0
weak var delegate: WatchKitConnectionDelegate?
private override init() {
@@ -72,27 +65,6 @@ extension WatchKitConnection: WatchKitConnectionProtocol {
WatchKitConnection.distance = dDistance
let dKcal = Double(result["kcal"] as! Double)
WatchKitConnection.kcal = dKcal
if let totalKcalDouble = result["totalKcal"] as? Double {
WatchKitConnection.totalKcal = totalKcalDouble
}
let dSpeed = Double(result["speed"] as! Double)
WatchKitConnection.speed = dSpeed
let dPower = Double(result["power"] as! Double)
WatchKitConnection.power = dPower
let dCadence = Double(result["cadence"] as! Double)
WatchKitConnection.cadence = dCadence
if let stepsDouble = result["steps"] as? Double {
let iSteps = Int(stepsDouble)
WatchKitConnection.steps = iSteps
}
if let elevationGainDouble = result["elevationGain"] as? Double {
WatchKitConnection.elevationGain = elevationGainDouble
// Calculate flights climbed and update WorkoutTracking
let flightsClimbed = elevationGainDouble / 3.048 // One flight = 10 feet = 3.048 meters
WorkoutTracking.flightsClimbed = flightsClimbed
print("WatchKitConnection: Received elevation gain: \(elevationGainDouble)m, flights: \(flightsClimbed)")
}
}, errorHandler: { (error) in
print(error)
})

View File

@@ -12,7 +12,6 @@ import HealthKit
protocol WorkoutTrackingDelegate: class {
func didReceiveHealthKitHeartRate(_ heartRate: Double)
func didReceiveHealthKitStepCounts(_ stepCounts: Double)
func didReceiveHealthKitStepCadence(_ stepCadence: Double)
func didReceiveHealthKitDistanceCycling(_ distanceCycling: Double)
func didReceiveHealthKitActiveEnergyBurned(_ activeEnergyBurned: Double)
}
@@ -28,27 +27,16 @@ class WorkoutTracking: NSObject {
static let shared = WorkoutTracking()
public static var distance = Double()
public static var kcal = Double()
public static var totalKcal = Double()
public static var cadenceTimeStamp = NSDate().timeIntervalSince1970
public static var cadenceLastSteps = Double()
public static var cadenceSteps = 0
public static var speed = Double()
public static var power = Double()
public static var steps = Int()
public static var cadence = Double()
public static var lastDateMetric = Date()
public static var flightsClimbed = Double()
var sport: Int = 0
let healthStore = HKHealthStore()
let configuration = HKWorkoutConfiguration()
var workoutSession: HKWorkoutSession!
var workoutBuilder: HKLiveWorkoutBuilder!
weak var delegate: WorkoutTrackingDelegate?
override init() {
super.init()
}
}
}
extension WorkoutTracking {
@@ -56,26 +44,20 @@ extension WorkoutTracking {
switch statistics.quantityType {
case HKQuantityType.quantityType(forIdentifier: .distanceCycling):
let distanceUnit = HKUnit.mile()
guard let value = statistics.mostRecentQuantity()?.doubleValue(for: distanceUnit) else {
return
}
let roundedValue = Double( round( 1 * value ) / 1 )
let value = statistics.mostRecentQuantity()?.doubleValue(for: distanceUnit)
let roundedValue = Double( round( 1 * value! ) / 1 )
delegate?.didReceiveHealthKitDistanceCycling(roundedValue)
case HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned):
let energyUnit = HKUnit.kilocalorie()
guard let value = statistics.mostRecentQuantity()?.doubleValue(for: energyUnit) else {
return
}
let roundedValue = Double( round( 1 * value ) / 1 )
let value = statistics.mostRecentQuantity()?.doubleValue(for: energyUnit)
let roundedValue = Double( round( 1 * value! ) / 1 )
delegate?.didReceiveHealthKitActiveEnergyBurned(roundedValue)
case HKQuantityType.quantityType(forIdentifier: .heartRate):
let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
guard let value = statistics.mostRecentQuantity()?.doubleValue(for: heartRateUnit) else {
return
}
let roundedValue = Double( round( 1 * value ) / 1 )
let value = statistics.mostRecentQuantity()?.doubleValue(for: heartRateUnit)
let roundedValue = Double( round( 1 * value! ) / 1 )
delegate?.didReceiveHealthKitHeartRate(roundedValue)
case HKQuantityType.quantityType(forIdentifier: .stepCount):
@@ -97,13 +79,7 @@ extension WorkoutTracking {
if let sum = result.sumQuantity() {
resultCount = sum.doubleValue(for: HKUnit.count())
let now = NSDate().timeIntervalSince1970
let deltaT = now - WorkoutTracking.cadenceTimeStamp
let deltaC = resultCount - WorkoutTracking.cadenceLastSteps
WorkoutTracking.cadenceLastSteps = resultCount
WorkoutTracking.cadenceTimeStamp = now
weakSelf.delegate?.didReceiveHealthKitStepCounts(resultCount)
weakSelf.delegate?.didReceiveHealthKitStepCadence((deltaC / deltaT) * 60)
} else {
print("Failed to fetch steps rate 2")
}
@@ -115,23 +91,8 @@ extension WorkoutTracking {
}
}
func setSport(_ sport: Int) {
self.sport = sport
}
private func configWorkout() {
var activityType = HKWorkoutActivityType.cycling
if self.sport == 1 {
activityType = HKWorkoutActivityType.running
} else if self.sport == 2 {
activityType = HKWorkoutActivityType.walking
} else if self.sport == 3 {
activityType = HKWorkoutActivityType.elliptical
} else if self.sport == 4 {
activityType = HKWorkoutActivityType.rowing
}
configuration.activityType = activityType
configuration.activityType = .cycling
configuration.locationType = .indoor
do {
@@ -159,41 +120,13 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
HKSampleType.workoutType()
])
var infoToShare: Set<HKSampleType> = []
if #available(watchOSApplicationExtension 10.0, *) {
infoToShare = Set([
HKSampleType.quantityType(forIdentifier: .stepCount)!,
HKSampleType.quantityType(forIdentifier: .heartRate)!,
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
HKSampleType.quantityType(forIdentifier: .basalEnergyBurned)!,
HKSampleType.quantityType(forIdentifier: .cyclingPower)!,
HKSampleType.quantityType(forIdentifier: .cyclingSpeed)!,
HKSampleType.quantityType(forIdentifier: .cyclingCadence)!,
HKSampleType.quantityType(forIdentifier: .runningPower)!,
HKSampleType.quantityType(forIdentifier: .runningSpeed)!,
HKSampleType.quantityType(forIdentifier: .runningStrideLength)!,
HKSampleType.quantityType(forIdentifier: .runningVerticalOscillation)!,
HKSampleType.quantityType(forIdentifier: .walkingSpeed)!,
HKSampleType.quantityType(forIdentifier: .walkingStepLength)!,
HKSampleType.quantityType(forIdentifier: .flightsClimbed)!,
HKSampleType.workoutType()
])
} else {
// Fallback on earlier versions
infoToShare = Set([
HKSampleType.quantityType(forIdentifier: .stepCount)!,
HKSampleType.quantityType(forIdentifier: .heartRate)!,
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
HKSampleType.quantityType(forIdentifier: .basalEnergyBurned)!,
HKSampleType.quantityType(forIdentifier: .flightsClimbed)!,
HKSampleType.workoutType()
])
}
let infoToShare = Set([
HKSampleType.quantityType(forIdentifier: .stepCount)!,
HKSampleType.quantityType(forIdentifier: .heartRate)!,
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
HKSampleType.workoutType()
])
HKHealthStore().requestAuthorization(toShare: infoToShare, read: infoToRead) { (success, error) in
if success {
@@ -208,9 +141,6 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
}
func startWorkOut() {
WorkoutTracking.lastDateMetric = Date()
// Reset flights climbed for new workout
WorkoutTracking.flightsClimbed = 0
print("Start workout")
configWorkout()
workoutSession.startActivity(with: Date())
@@ -219,10 +149,6 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
if let error = error {
print(error)
}
if self.sport > 0 {
self.workoutBuilder.dataSource?.enableCollection(for: HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)!, predicate: nil)
}
}
}
@@ -231,209 +157,44 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
workoutSession.stopActivity(with: Date())
workoutSession.end()
// Write active calories
guard let activeQuantityType = HKQuantityType.quantityType(
guard let quantityType = HKQuantityType.quantityType(
forIdentifier: .activeEnergyBurned) else {
return
}
let unit = HKUnit.kilocalorie()
let activeEnergyBurned = WorkoutTracking.kcal
let activeQuantity = HKQuantity(unit: unit,
doubleValue: activeEnergyBurned)
let totalEnergyBurned = WorkoutTracking.kcal
let quantity = HKQuantity(unit: unit,
doubleValue: totalEnergyBurned)
let startDate = workoutSession.startDate ?? WorkoutTracking.lastDateMetric
let sample = HKCumulativeQuantitySeriesSample(type: quantityType,
quantity: quantity,
start: workoutSession.startDate!,
end: Date())
let activeSample = HKCumulativeQuantitySeriesSample(type: activeQuantityType,
quantity: activeQuantity,
start: startDate,
end: Date())
workoutBuilder.add([sample]) {(success, error) in}
workoutBuilder.add([activeSample]) {(success, error) in
if let error = error {
print("WatchWorkoutTracking active calories: \(error.localizedDescription)")
}
guard let quantityTypeDistance = HKQuantityType.quantityType(
forIdentifier: .distanceCycling) else {
return
}
let unitDistance = HKUnit.mile()
let miles = WorkoutTracking.distance
let quantityMiles = HKQuantity(unit: unitDistance,
doubleValue: miles)
if(sport == 0) {
guard let quantityTypeDistance = HKQuantityType.quantityType(
forIdentifier: .distanceCycling) else {
return
}
let sampleDistance = HKCumulativeQuantitySeriesSample(type: quantityTypeDistance,
quantity: quantityMiles,
start: startDate,
end: Date())
workoutBuilder.add([sampleDistance]) {(success, error) in
if let error = error {
print(error)
}
self.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
if let error = error {
print(error)
}
self.workoutBuilder.finishWorkout{ (workout, error) in
if let error = error {
print(error)
}
workout?.setValue(quantityMiles, forKey: "totalDistance")
// Set total energy burned on the workout
let totalEnergy = WorkoutTracking.totalKcal > 0 ? WorkoutTracking.totalKcal : activeEnergyBurned
let totalEnergyQuantity = HKQuantity(unit: unit, doubleValue: totalEnergy)
workout?.setValue(totalEnergyQuantity, forKey: "totalEnergyBurned")
}
}
}
} else if(sport == 4) { // Rowing
// Guard to check if steps quantity type is available
guard let quantityTypeSteps = HKQuantityType.quantityType(
forIdentifier: .stepCount) else {
return
}
let stepsQuantity = HKQuantity(unit: HKUnit.count(), doubleValue: Double(WorkoutTracking.steps))
// Create a sample for total steps
let sampleSteps = HKCumulativeQuantitySeriesSample(
type: quantityTypeSteps,
quantity: stepsQuantity,
start: startDate,
end: Date())
// Add the steps sample to workout builder
workoutBuilder.add([sampleSteps]) { (success, error) in
if let error = error {
print(error)
}
}
// Per il rowing, HealthKit utilizza un tipo specifico di distanza
// Se non esiste un tipo specifico per il rowing, possiamo usare un tipo generico di distanza
var quantityTypeDistance: HKQuantityType?
// In watchOS 10 e versioni successive, possiamo usare un tipo specifico se disponibile
if #available(watchOSApplicationExtension 10.0, *) {
// Verifica se esiste un tipo specifico per il rowing, altrimenti utilizza un tipo generico
quantityTypeDistance = HKQuantityType.quantityType(forIdentifier: .distanceSwimming)
} else {
// Nelle versioni precedenti, usa il tipo generico
quantityTypeDistance = HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)
}
guard let typeDistance = quantityTypeDistance else {
return
}
let sampleDistance = HKCumulativeQuantitySeriesSample(type: typeDistance,
quantity: quantityMiles,
start: startDate,
end: Date())
workoutBuilder.add([sampleDistance]) {(success, error) in
if let error = error {
print(error)
}
self.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
if let error = error {
print(error)
}
self.workoutBuilder.finishWorkout{ (workout, error) in
if let error = error {
print(error)
}
workout?.setValue(quantityMiles, forKey: "totalDistance")
// Set total energy burned on the workout
let totalEnergy = WorkoutTracking.totalKcal > 0 ? WorkoutTracking.totalKcal : activeEnergyBurned
let totalEnergyQuantity = HKQuantity(unit: unit, doubleValue: totalEnergy)
workout?.setValue(totalEnergyQuantity, forKey: "totalEnergyBurned")
}
}
}
} else {
// Guard to check if steps quantity type is available
guard let quantityTypeSteps = HKQuantityType.quantityType(
forIdentifier: .stepCount) else {
return
}
let stepsQuantity = HKQuantity(unit: HKUnit.count(), doubleValue: Double(WorkoutTracking.steps))
// Create a sample for total steps
let sampleSteps = HKCumulativeQuantitySeriesSample(
type: quantityTypeSteps,
quantity: stepsQuantity, // Use your steps quantity here
start: startDate,
end: Date())
// Guard to check if distance quantity type is available
guard let quantityTypeDistance = HKQuantityType.quantityType(
forIdentifier: .distanceWalkingRunning) else {
return
}
let sampleDistance = HKCumulativeQuantitySeriesSample(type: quantityTypeDistance,
quantity: quantityMiles,
start: startDate,
end: Date())
// Create flights climbed sample if available
var samplesToAdd: [HKCumulativeQuantitySeriesSample] = [sampleSteps, sampleDistance]
if WorkoutTracking.flightsClimbed > 0 {
if let quantityTypeFlights = HKQuantityType.quantityType(forIdentifier: .flightsClimbed) {
let flightsQuantity = HKQuantity(unit: HKUnit.count(), doubleValue: WorkoutTracking.flightsClimbed)
let sampleFlights = HKCumulativeQuantitySeriesSample(
type: quantityTypeFlights,
quantity: flightsQuantity,
start: startDate,
end: Date())
samplesToAdd.append(sampleFlights)
print("WatchWorkoutTracking: Adding flights climbed to workout: \(WorkoutTracking.flightsClimbed)")
}
}
// Add all samples to the workout builder
workoutBuilder.add(samplesToAdd) { (success, error) in
if let error = error {
print(error)
}
// End the data collection
self.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
if let error = error {
print(error)
}
// Finish the workout and save metrics
self.workoutBuilder.finishWorkout { (workout, error) in
if let error = error {
print(error)
}
workout?.setValue(stepsQuantity, forKey: "totalSteps")
workout?.setValue(quantityMiles, forKey: "totalDistance")
// Set total energy burned on the workout
let totalEnergy = WorkoutTracking.totalKcal > 0 ? WorkoutTracking.totalKcal : activeEnergyBurned
let totalEnergyQuantity = HKQuantity(unit: unit, doubleValue: totalEnergy)
workout?.setValue(totalEnergyQuantity, forKey: "totalEnergyBurned")
// Reset flights climbed for next workout
WorkoutTracking.flightsClimbed = 0
}
}
}
}
let sampleDistance = HKCumulativeQuantitySeriesSample(type: quantityTypeDistance,
quantity: quantityMiles,
start: workoutSession.startDate!,
end: Date())
workoutBuilder.add([sample]) {(success, error) in}
workoutBuilder.add([sampleDistance]) {(success, error) in}
workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
}
}
func fetchStepCounts() {
@@ -442,7 +203,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
}
let startOfDay = Calendar.current.startOfDay(for: Date())
let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: Date(), options: .strictStartDate)
let query = HKStatisticsQuery(quantityType: stepCounts, quantitySamplePredicate: predicate, options: .cumulativeSum) { [weak self] (_, result, error) in
guard let weakSelf = self else {
return
@@ -452,7 +213,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
print("Failed to fetch steps rate")
return
}
if let sum = result.sumQuantity() {
resultCount = sum.doubleValue(for: HKUnit.count())
weakSelf.delegate?.didReceiveHealthKitStepCounts(resultCount)
@@ -476,135 +237,6 @@ extension WorkoutTracking: HKLiveWorkoutBuilderDelegate {
handleSendStatisticsData(statistics)
}
}
if(sport == 0) {
if #available(watchOSApplicationExtension 10.0, *) {
let wattPerInterval = HKQuantity(unit: HKUnit.watt(),
doubleValue: WorkoutTracking.power)
if(WorkoutTracking.lastDateMetric.distance(to: Date()) < 1) {
return
}
guard let powerType = HKQuantityType.quantityType(
forIdentifier: .cyclingPower) else {
return
}
let wattPerIntervalSample = HKQuantitySample(type: powerType,
quantity: wattPerInterval,
start: WorkoutTracking.lastDateMetric,
end: Date())
workoutBuilder.add([wattPerIntervalSample]) {(success, error) in
if let error = error {
print(error)
}
}
let cadencePerInterval = HKQuantity(unit: HKUnit.count().unitDivided(by: HKUnit.second()),
doubleValue: WorkoutTracking.cadence / 60.0)
guard let cadenceType = HKQuantityType.quantityType(
forIdentifier: .cyclingCadence) else {
return
}
let cadencePerIntervalSample = HKQuantitySample(type: cadenceType,
quantity: cadencePerInterval,
start: WorkoutTracking.lastDateMetric,
end: Date())
workoutBuilder.add([cadencePerIntervalSample]) {(success, error) in
if let error = error {
print(error)
}
}
let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
doubleValue: WorkoutTracking.speed * 0.277778)
guard let speedType = HKQuantityType.quantityType(
forIdentifier: .cyclingSpeed) else {
return
}
let speedPerIntervalSample = HKQuantitySample(type: speedType,
quantity: speedPerInterval,
start: WorkoutTracking.lastDateMetric,
end: Date())
workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
if let error = error {
print(error)
}
}
} else {
// Fallback on earlier versions
}
} else if(sport == 1) {
if #available(watchOSApplicationExtension 10.0, *) {
let wattPerInterval = HKQuantity(unit: HKUnit.watt(),
doubleValue: WorkoutTracking.power)
if(WorkoutTracking.lastDateMetric.distance(to: Date()) < 1) {
return
}
guard let powerType = HKQuantityType.quantityType(
forIdentifier: .runningPower) else {
return
}
let wattPerIntervalSample = HKQuantitySample(type: powerType,
quantity: wattPerInterval,
start: WorkoutTracking.lastDateMetric,
end: Date())
workoutBuilder.add([wattPerIntervalSample]) {(success, error) in
if let error = error {
print(error)
}
}
let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
doubleValue: WorkoutTracking.speed * 0.277778)
guard let speedType = HKQuantityType.quantityType(
forIdentifier: .runningSpeed) else {
return
}
let speedPerIntervalSample = HKQuantitySample(type: speedType,
quantity: speedPerInterval,
start: WorkoutTracking.lastDateMetric,
end: Date())
workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
if let error = error {
print(error)
}
}
} else {
// Fallback on earlier versions
}
} else if(sport == 2) {
if #available(watchOSApplicationExtension 10.0, *) {
let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
doubleValue: WorkoutTracking.speed * 0.277778)
guard let speedType = HKQuantityType.quantityType(
forIdentifier: .walkingSpeed) else {
return
}
let speedPerIntervalSample = HKQuantitySample(type: speedType,
quantity: speedPerInterval,
start: WorkoutTracking.lastDateMetric,
end: Date())
workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
if let error = error {
print(error)
}
}
} else {
// Fallback on earlier versions
}
}
WorkoutTracking.lastDateMetric = Date()
}
func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {

View File

@@ -26,13 +26,6 @@
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "watch",
"role" : "notificationCenter",
"scale" : "2x",
"size" : "33x33",
"subtype" : "45mm"
},
{
"idiom" : "watch",
"role" : "appLauncher",
@@ -47,13 +40,6 @@
"size" : "44x44",
"subtype" : "40mm"
},
{
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "46x46",
"subtype" : "41mm"
},
{
"idiom" : "watch",
"role" : "appLauncher",
@@ -61,13 +47,6 @@
"size" : "50x50",
"subtype" : "44mm"
},
{
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "51x51",
"subtype" : "45mm"
},
{
"idiom" : "watch",
"role" : "quickLook",
@@ -89,13 +68,6 @@
"size" : "108x108",
"subtype" : "44mm"
},
{
"idiom" : "watch",
"role" : "quickLook",
"scale" : "2x",
"size" : "117x117",
"subtype" : "45mm"
},
{
"idiom" : "watch-marketing",
"scale" : "1x",

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Some files were not shown because too many files have changed in this diff Show More