-
Notifications
You must be signed in to change notification settings - Fork 2
Add support for iOS target #269
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3cc0818
feca8e0
86a667d
de4c96c
4e7bab5
c5614bd
bbfeee3
5981217
8db8e97
5f6499c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,16 +10,21 @@ on: | |||||||||||||
|
|
||||||||||||||
| jobs: | ||||||||||||||
| build: | ||||||||||||||
| name: 🔨 Build | ||||||||||||||
| runs-on: ubuntu-24.04 | ||||||||||||||
| name: 🔨 Build - ${{ matrix.name }} | ||||||||||||||
| runs-on: ${{ matrix.os }} | ||||||||||||||
| strategy: | ||||||||||||||
| fail-fast: false | ||||||||||||||
| matrix: | ||||||||||||||
| include: | ||||||||||||||
| - name: 📱 Android App | ||||||||||||||
| gradle_module: tasks-app-android | ||||||||||||||
| os: ubuntu-24.04 | ||||||||||||||
| - name: 🖥️ Desktop App | ||||||||||||||
| gradle_module: tasks-app-desktop | ||||||||||||||
| os: ubuntu-24.04 | ||||||||||||||
| - name: 🍎 iOS App | ||||||||||||||
| gradle_module: tasks-app-ios | ||||||||||||||
| os: macos-15 | ||||||||||||||
| permissions: | ||||||||||||||
| contents: write | ||||||||||||||
| checks: write | ||||||||||||||
|
|
@@ -30,12 +35,24 @@ jobs: | |||||||||||||
| - uses: actions/checkout@v4 | ||||||||||||||
| - uses: ./.github/actions/setup-jdk-gradle | ||||||||||||||
|
|
||||||||||||||
| - name: Cache Gradle | ||||||||||||||
| if: ${{ matrix.gradle_module == 'tasks-app-ios' }} | ||||||||||||||
| uses: actions/cache@v4 | ||||||||||||||
| with: | ||||||||||||||
| path: | | ||||||||||||||
| .gradle | ||||||||||||||
| $HOME/.m2/repository | ||||||||||||||
| $HOME/.konan | ||||||||||||||
| key: gradle-${{ runner.os }}-${{ hashFiles('gradle/libs.versions.toml', 'gradle/wrapper/gradle-wrapper.properties', '**/*.gradle.kts', '**/*.gradle') }} | ||||||||||||||
| restore-keys: | | ||||||||||||||
| gradle-${{ runner.os }}- | ||||||||||||||
|
|
||||||||||||||
| - name: 🔓 Decrypt secrets | ||||||||||||||
| env: | ||||||||||||||
| PLAYSTORE_SECRET_PASSPHRASE: ${{ secrets.PLAYSTORE_SECRET_PASSPHRASE }} | ||||||||||||||
| run: ./_ci/decrypt_secrets.sh | ||||||||||||||
|
|
||||||||||||||
| - name: ${{ matrix.name }} | ||||||||||||||
| - name: ${{ matrix.gradle_module }} | ||||||||||||||
| env: | ||||||||||||||
| PLAYSTORE_SECRET_PASSPHRASE: ${{ secrets.PLAYSTORE_SECRET_PASSPHRASE }} | ||||||||||||||
| KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} | ||||||||||||||
|
|
@@ -50,4 +67,6 @@ jobs: | |||||||||||||
| -Pplaystore.keystore.file="${PWD}/_ci/tasksApp.keystore" \ | ||||||||||||||
| -Pplaystore.keystore.password="${KEYSTORE_PASSWORD}" \ | ||||||||||||||
| -Pplaystore.keystore.key_password="${KEYSTORE_KEY_PASSWORD}" | ||||||||||||||
| elif [ "${gradle_module}" = "tasks-app-ios" ]; then | ||||||||||||||
| IOS_TARGET=simulator ./gradlew tasks-app-shared:linkDebugFrameworkIosSimulatorArm64 | ||||||||||||||
| fi | ||||||||||||||
|
Comment on lines
+70
to
72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive)
The environment variable is exported but the Gradle task you call ( - elif [ "${gradle_module}" = "tasks-app-ios" ]; then
- IOS_TARGET=simulator ./gradlew tasks-app-shared:linkDebugFrameworkIosSimulatorArm64
+ elif [ "${gradle_module}" = "tasks-app-ios" ]; then
+ ./gradlew tasks-app-shared:linkDebugFrameworkIosSimulatorArm64📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -7,7 +7,7 @@ on: | |||||
|
|
||||||
| jobs: | ||||||
| check-changes: | ||||||
| runs-on: ubuntu-latest | ||||||
| runs-on: ubuntu-24.04 | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Prefer Pinning to a specific Ubuntu image means you will miss future - runs-on: ubuntu-24.04
+ runs-on: ubuntu-latest📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| outputs: | ||||||
| changes-detected: ${{ steps.check.outputs.changes_detected }} | ||||||
| steps: | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,73 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| name: 🍎 iOS App Nightly | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||||||||||||||||||
| schedule: | ||||||||||||||||||||||||||||||||||||||||||||||||
| - cron: '0 2 * * *' | ||||||||||||||||||||||||||||||||||||||||||||||||
| workflow_dispatch: | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+3
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Quote the “on” key to silence YAMLlint truthy warning. GitHub Actions accepts quoted keys; this keeps linters quiet. -on:
+"on":
schedule:
- cron: '0 2 * * *'
workflow_dispatch:📝 Committable suggestion
Suggested change
🧰 Tools🪛 YAMLlint (1.37.1)[warning] 3-3: truthy value should be one of [false, true] (truthy) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||||||||||||||||||
| check-changes: | ||||||||||||||||||||||||||||||||||||||||||||||||
| runs-on: macos-15 | ||||||||||||||||||||||||||||||||||||||||||||||||
| outputs: | ||||||||||||||||||||||||||||||||||||||||||||||||
| changes-detected: ${{ steps.check.outputs.changes_detected }} | ||||||||||||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||||||||||||
| - uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||
| fetch-depth: 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Check for changes | ||||||||||||||||||||||||||||||||||||||||||||||||
| id: check | ||||||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||||||
| git fetch origin main | ||||||||||||||||||||||||||||||||||||||||||||||||
| if git log --since="24 hours ago" --pretty=format:%H -- . \ | ||||||||||||||||||||||||||||||||||||||||||||||||
| ':(exclude)website/' \ | ||||||||||||||||||||||||||||||||||||||||||||||||
| ':(exclude)fastlane/' \ | ||||||||||||||||||||||||||||||||||||||||||||||||
| ':(exclude)assets/' \ | ||||||||||||||||||||||||||||||||||||||||||||||||
| ':(exclude)**/*.md' \ | ||||||||||||||||||||||||||||||||||||||||||||||||
| | grep .; then | ||||||||||||||||||||||||||||||||||||||||||||||||
| echo "changes_detected=true" >> "$GITHUB_OUTPUT" | ||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||
| echo "changes_detected=false" >> "$GITHUB_OUTPUT" | ||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||
| build-ios-app: | ||||||||||||||||||||||||||||||||||||||||||||||||
| timeout-minutes: 15 | ||||||||||||||||||||||||||||||||||||||||||||||||
| needs: check-changes | ||||||||||||||||||||||||||||||||||||||||||||||||
| if: needs.check-changes.outputs.changes-detected == 'true' || github.event_name == 'workflow_dispatch' | ||||||||||||||||||||||||||||||||||||||||||||||||
| name: 🍎 Build iOS App | ||||||||||||||||||||||||||||||||||||||||||||||||
| runs-on: macos-15 | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||||||||||||
| - uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||||||||||||||||||
| - uses: ./.github/actions/setup-jdk-gradle | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Cache Gradle | ||||||||||||||||||||||||||||||||||||||||||||||||
| if: ${{ matrix.gradle_module == 'tasks-app-ios' }} | ||||||||||||||||||||||||||||||||||||||||||||||||
| uses: actions/cache@v4 | ||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||
| path: | | ||||||||||||||||||||||||||||||||||||||||||||||||
| .gradle | ||||||||||||||||||||||||||||||||||||||||||||||||
| $HOME/.m2/repository | ||||||||||||||||||||||||||||||||||||||||||||||||
| $HOME/.konan | ||||||||||||||||||||||||||||||||||||||||||||||||
| key: gradle-${{ runner.os }}-${{ hashFiles('gradle/libs.versions.toml', 'gradle/wrapper/gradle-wrapper.properties', '**/*.gradle.kts', '**/*.gradle') }} | ||||||||||||||||||||||||||||||||||||||||||||||||
| restore-keys: | | ||||||||||||||||||||||||||||||||||||||||||||||||
| gradle-${{ runner.os }}- | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+45
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove invalid There is no matrix defined for this job, so - if: ${{ matrix.gradle_module == 'tasks-app-ios' }}Either delete the 📝 Committable suggestion
Suggested change
🧰 Tools🪛 actionlint (1.7.7)45-45: property "gradle_module" is not defined in object type {} (expression) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
| - name: 🔓 Decrypt secrets | ||||||||||||||||||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||||||||||||||||||
| PLAYSTORE_SECRET_PASSPHRASE: ${{ secrets.PLAYSTORE_SECRET_PASSPHRASE }} | ||||||||||||||||||||||||||||||||||||||||||||||||
| run: ./_ci/decrypt_secrets.sh | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| - name: 🔨 Build | ||||||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||||||
| cd tasks-app-ios | ||||||||||||||||||||||||||||||||||||||||||||||||
| IOS_TARGET=simulator xcodebuild \ | ||||||||||||||||||||||||||||||||||||||||||||||||
| -project Taskfolio.xcodeproj \ | ||||||||||||||||||||||||||||||||||||||||||||||||
| -scheme Taskfolio \ | ||||||||||||||||||||||||||||||||||||||||||||||||
| -sdk iphonesimulator \ | ||||||||||||||||||||||||||||||||||||||||||||||||
| -arch arm64 \ | ||||||||||||||||||||||||||||||||||||||||||||||||
| -configuration Debug \ | ||||||||||||||||||||||||||||||||||||||||||||||||
| build \ | ||||||||||||||||||||||||||||||||||||||||||||||||
| CODE_SIGNING_ALLOWED=NO \ | ||||||||||||||||||||||||||||||||||||||||||||||||
| CODE_SIGN_IDENTITY="" \ | ||||||||||||||||||||||||||||||||||||||||||||||||
| CODE_SIGNING_REQUIRED=NO | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+63
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Harden the
- IOS_TARGET=simulator xcodebuild \
+ xcodebuild \
-project Taskfolio.xcodeproj \
-scheme Taskfolio \
-sdk iphonesimulator \
+ -destination "platform=iOS Simulator,name=iPhone 15" \
-arch arm64 \📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -33,6 +33,11 @@ local.properties | |||||||||||||||
| .kotlin/metadata/ | ||||||||||||||||
|
|
||||||||||||||||
| tasks-app-android/google-services.json | ||||||||||||||||
| tasks-app-desktop/bin/ | ||||||||||||||||
| tasks-app-ios/Taskfolio.xcodeproj/xcuserdata/*.xcuserdatad | ||||||||||||||||
| tasks-app-ios/Taskfolio.xcodeproj/project.xcworkspace/contents.xcworkspacedata | ||||||||||||||||
| tasks-app-ios/Taskfolio.xcodeproj/project.xcworkspace/xcuserdata/*.xcuserdatad | ||||||||||||||||
|
Comment on lines
+36
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Wildcard patterns could be more generic The new ignores work, but you can reduce future churn by covering all Xcode user data in one line: -tasks-app-ios/Taskfolio.xcodeproj/xcuserdata/*.xcuserdatad
-tasks-app-ios/Taskfolio.xcodeproj/project.xcworkspace/xcuserdata/*.xcuserdatad
+tasks-app-ios/**/*.xcuserdatadSame effect, less maintenance if more iOS modules appear. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||
| tasks-app-ios/Taskfolio.xcodeproj/project.xcworkspace/xcshareddata/* | ||||||||||||||||
|
|
||||||||||||||||
| *token_cache.* | ||||||||||||||||
| client_secret_*.apps.googleusercontent.com.json | ||||||||||||||||
|
|
@@ -42,4 +47,3 @@ _ci/api-*.json | |||||||||||||||
| bundletool-*.jar | ||||||||||||||||
|
|
||||||||||||||||
| _site/ | ||||||||||||||||
| tasks-app-desktop/bin/ | ||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,7 +9,7 @@ | |||||||||
|
|
||||||||||
| The coverage report excludes code not intended to be covered. | ||||||||||
|
|
||||||||||
| This avoids the [“broken window” effect](https://blog.codinghorror.com/the-broken-window-theory/): whether coverage is at 43% or 56%, it’s perceived as equally low—so efforts to improve it are often dismissed. In contrast, high or near-100% coverage is seen as achievable and worth tracking. | ||||||||||
| This avoids the [“broken window” effect](https://blog.codinghorror.com/the-broken-window-theory/): whether coverage is at 43% or 56%, it's perceived as equally low—so efforts to improve it are often dismissed. In contrast, high or near-100% coverage is seen as achievable and worth tracking. | ||||||||||
|
|
||||||||||
| Refer to the root project's [`build.gradle.kts`](build.gradle.kts#L55-L90) for details. | ||||||||||
|
|
||||||||||
|
|
@@ -36,13 +36,16 @@ Refer to the root project's [`build.gradle.kts`](build.gradle.kts#L55-L90) for d | |||||||||
|
|
||||||||||
| [**Taskfolio**](https://opatry.github.io/taskfolio) is an Android task management app built using [Google Tasks API](https://developers.google.com/tasks/reference/rest). Developed to demonstrate my expertise in modern Android development, it highlights my skills in architecture, UI design with Jetpack Compose, OAuth authentication, and more—all packaged in a sleek, user-friendly interface. | ||||||||||
|
|
||||||||||
| > I set out to revisit the classical TODO app, ‘local-first’ syncing with Google Tasks—aiming for an <abbr title="Minimum Viable Experience">MVE</abbr> in 2 weeks, focusing on the 80/20 rule to nail the essentials. | ||||||||||
| > I set out to revisit the classical TODO app, 'local-first' syncing with Google Tasks—aiming for an <abbr title="Minimum Viable Experience">MVE</abbr> in 2 weeks, focusing on the 80/20 rule to nail the essentials. | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Replace inline HTML with plain text for markdownlint compatibility. Avoid MD033 by expanding the abbreviation inline. -> I set out to revisit the classical TODO app, 'local-first' syncing with Google Tasks—aiming for an <abbr title="Minimum Viable Experience">MVE</abbr> in 2 weeks, focusing on the 80/20 rule to nail the essentials.
+> I set out to revisit the classical TODO app, 'local-first' syncing with Google Tasks—aiming for a Minimum Viable Experience (MVE) in 2 weeks, focusing on the 80/20 rule to nail the essentials.📝 Committable suggestion
Suggested change
🧰 Tools🪛 LanguageTool[grammar] ~39-~39: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) 🪛 markdownlint-cli2 (0.17.2)39-39: Inline HTML (MD033, no-inline-html) 🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| |  |  |  |  | | ||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
| | --------------------------------------- |--------------------------------------- | ---------------------------------- | ---------------------------------- | | ||||||||||
|
|
||||||||||
| [](https://play.google.com/store/apps/details?id=net.opatry.tasks.app) | ||||||||||
|
|
||||||||||
| > [!NOTE] | ||||||||||
| > The application is also available as a desktop (Jvm) application and an iOS application as well (using [Compose Multi Platform (aka CMP)](https://www.jetbrains.com/compose-multiplatform/) as UI Toolkit). | ||||||||||
|
|
||||||||||
|
Comment on lines
+46
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Use the official naming “Compose Multiplatform”. Minor terminology nit for consistency. -> The application is also available as a desktop (Jvm) application and an iOS application as well (using [Compose Multi Platform (aka CMP)](https://www.jetbrains.com/compose-multiplatform/) as UI Toolkit).
+> The application is also available as a desktop (JVM) application and an iOS application (using [Compose Multiplatform](https://www.jetbrains.com/compose-multiplatform/) as the UI toolkit).📝 Committable suggestion
Suggested change
🧰 Tools🪛 LanguageTool[grammar] ~46-~46: There might be a mistake here. (QB_NEW_EN) [grammar] ~47-~47: There might be a mistake here. (QB_NEW_EN_OTHER) [grammar] ~47-~47: There might be a mistake here. (QB_NEW_EN) [grammar] ~47-~47: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) 🤖 Prompt for AI Agents |
||||||||||
| ## 🎯 Project intentions | ||||||||||
|
|
||||||||||
| - [x] Showcase my expertise in Android application development | ||||||||||
|
|
@@ -76,9 +79,10 @@ I do not aim to implement advanced features beyond what is supported by the Goog | |||||||||
|
|
||||||||||
| ## 🛠️ Tech stack | ||||||||||
|
|
||||||||||
| - [Kotlin](https://kotlinlang.org/), [Multiplatform (aka KMP)](https://kotlinlang.org/docs/multiplatform.html) (currently Desktop & Android are supported) | ||||||||||
| - iOS wasn’t initially planned, but I bootstrapped a [PR to evaluate the feasibility of the iOS target]((https://github.com/opatry/taskfolio/pull/269)). It turned out to be quite achievable and just needs some polishing. | ||||||||||
| - Web is not planned any time soon (contribution are welcome 🤝) | ||||||||||
| - [Kotlin](https://kotlinlang.org/), [Multiplatform (aka KMP)](https://kotlinlang.org/docs/multiplatform.html) | ||||||||||
| - Android and Desktop are fully supported. | ||||||||||
| - iOS wasn't initially planned, but a draft version is available (use it at your own risk, there might be dragons 🐉). | ||||||||||
| - Web is not planned any time soon (contributions are welcome 🤝) | ||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
| - [Kotlin coroutines](https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html) | ||||||||||
| - [Ktor client](https://ktor.io/) (+ [Kotlinx serialization](https://kotlinlang.org/docs/serialization.html)) | ||||||||||
| - [Room](https://developer.android.com/training/data-storage/room) for local persistence | ||||||||||
|
|
@@ -123,6 +127,9 @@ I do not aim to implement advanced features beyond what is supported by the Goog | |||||||||
| - The Desktop application (thin layer fully reusing `:tasks-app-shared`) | ||||||||||
| - [`:tasks-app-android`](tasks-app-android) <span style="color: #66FF00;">■■■■■■■■</span>□□ 80% | ||||||||||
| - The Android application (thin layer fully reusing `:tasks-app-shared`) | ||||||||||
| - [`:tasks-app-ios/Taskfolio`](tasks-app-ios/Taskfolio) <span style="color: #33FF00;">■■■■■■■■■</span>□ 90% | ||||||||||
| - The iOS application (thin layer fully reusing `:tasks-app-shared`) | ||||||||||
| - Xcode project, written in Swift | ||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
| - [`website/`](website) <span style="color: #00FF00;">■■■■■■■■■■</span> 100% | ||||||||||
| - The [static site](https://opatry.github.io/taskfolio/) presenting the project | ||||||||||
| - Made with [Jekyll](https://jekyllrb.com/) and served by [Github pages](https://pages.github.com/) | ||||||||||
|
|
@@ -177,6 +184,68 @@ When clicking on it, it will open a new window with the hot reload status. | |||||||||
|  | ||||||||||
| </details> | ||||||||||
|
|
||||||||||
| ## 🍎 Build for iOS target | ||||||||||
|
|
||||||||||
| The support of iOS works more or less _as-is_ and gets the job done. It's provided without guarantees, use at your own risk. | ||||||||||
| Feedback and contributions are welcome though 🤝. | ||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
|
|
||||||||||
| > [!NOTE] | ||||||||||
| > iOS support is _opt-in_ and disabled by default to avoid unnecessary time and disk usage during the initial Gradle sync when the iOS target isn't required. | ||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
| > You can enable it by setting `ios.target` Gradle property to `all`, `simulator` or `device` from either `local.properties` or CLI using `-P`. | ||||||||||
| > When building from Xcode, it automatically sets `-Pios.target=simulator` based on `Config.xcconfig`. | ||||||||||
|
|
||||||||||
| <details> | ||||||||||
| <summary>See details…</summary> | ||||||||||
|
|
||||||||||
| You can build the `:tasks-app-shared` code for iOS using Gradle (to check if everything compiles on Kotlin side): | ||||||||||
|
|
||||||||||
| ```bash | ||||||||||
| ./gradlew tasks-app-shared:linkDebugFrameworkIosSimulatorArm64 -Pios.target=simulator | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| ### Building & Running from IntelliJ/Android Studio | ||||||||||
|
|
||||||||||
| You can also use the incubating [Kotlin Multiplatform IntelliJ plugin](https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform) to build and launch the iOS app directly from IntelliJ/Android Studio (starting from Narwhal | 2025.1.1). | ||||||||||
| This plugin allows you to choose whether to run the app on a device or simulator, and enables debugging of Kotlin code even when called from iOS/Swift. | ||||||||||
|
|
||||||||||
| It builds the Kotlin code as a native framework, then triggers the appropriate Gradle task to build Kotlin first, followed by `xcodebuild` for the Xcode and iOS-specific parts, ensuring a seamless integration between Kotlin and Swift code (see next section for details). | ||||||||||
|
|
||||||||||
| ### Building & Running from Xcode | ||||||||||
|
|
||||||||||
| For full XCFramework build (to be consumed by the iOS application), you'll have to rely on `xcodebuild` (or build directly from Xcode): | ||||||||||
|
|
||||||||||
| ```bash | ||||||||||
| cd tasks-app-ios | ||||||||||
| IOS_TARGET=simulator xcodebuild -project Taskfolio.xcodeproj \ | ||||||||||
| -scheme Taskfolio \ | ||||||||||
| -sdk iphonesimulator \ | ||||||||||
| -arch arm64 \ | ||||||||||
| -configuration Debug \ | ||||||||||
| build \ | ||||||||||
| CODE_SIGNING_ALLOWED=NO \ | ||||||||||
| CODE_SIGN_IDENTITY="" \ | ||||||||||
| CODE_SIGNING_REQUIRED=NO | ||||||||||
| ``` | ||||||||||
| This triggers the `:tasks-app-shared:embedAndSignAppleFrameworkForXcode` Gradle task under the hood. | ||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+215
to
+229
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Fix formatting and remove unused env var in the xcodebuild example.
-```bash
-cd tasks-app-ios
-IOS_TARGET=simulator xcodebuild -project Taskfolio.xcodeproj \
+```bash
+cd tasks-app-ios
+xcodebuild -project Taskfolio.xcodeproj \
-scheme Taskfolio \
-sdk iphonesimulator \
-arch arm64 \
-configuration Debug \
build \
CODE_SIGNING_ALLOWED=NO \
CODE_SIGN_IDENTITY="" \
CODE_SIGNING_REQUIRED=NO
-```
+```🧰 Tools🪛 LanguageTool[grammar] ~215-~215: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) [grammar] ~229-~229: Use correct spacing (QB_NEW_EN_OTHER_ERROR_IDS_5) 🪛 markdownlint-cli2 (0.17.2)228-228: Fenced code blocks should be surrounded by blank lines (MD031, blanks-around-fences) 🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| For Xcode integration, it's recommended to install the [Xcode Kotlin plugin](https://touchlab.co/xcodekotlin): | ||||||||||
|
|
||||||||||
| ```bash | ||||||||||
| brew install xcode-kotlin | ||||||||||
| xcode-kotlin install | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| When you update Xcode, you'll have to sync the plugin: | ||||||||||
|
|
||||||||||
| ```bash | ||||||||||
| xcode-kotlin sync | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| If you want to debug the Kotlin code from Xcode, you'll have to add the needed source sets in Xcode: | ||||||||||
| Add Group > Add folders as **reference** > `tasks-app-shared/{commonMain,iosMain}` (or any other module you want to debug). | ||||||||||
| If you properly installed the Xcode Kotlin plugin, you'll be able to set a breakpoint in the Kotlin code and see syntax coloring as well. | ||||||||||
| </details> | ||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
|
|
||||||||||
| ## ⚖️ License | ||||||||||
|
|
||||||||||
| ``` | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ | |
|
|
||
| import kotlinx.kover.gradle.plugin.dsl.CoverageUnit | ||
| import org.gradle.api.tasks.testing.logging.TestExceptionFormat | ||
| import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension | ||
|
|
||
| plugins { | ||
| alias(libs.plugins.jetbrains.kotlin.multiplatform) apply false | ||
|
|
@@ -135,7 +136,32 @@ kover { | |
| } | ||
| } | ||
|
|
||
| private val kmpPluginId = libs.plugins.jetbrains.kotlin.multiplatform.get().pluginId | ||
| subprojects { | ||
| plugins.withId(kmpPluginId) { | ||
| if (project == project(":google:oauth-http")) return@withId | ||
|
|
||
| extensions.configure<KotlinMultiplatformExtension> { | ||
| // foo-bar-zorg → FooBarZorg | ||
| val frameworkBaseName = project.name.split('-').joinToString("") { part -> | ||
| part.replaceFirstChar(Char::uppercase) | ||
| } | ||
|
Comment on lines
+146
to
+148
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Locale-dependent capitalisation may yield inconsistent framework names
- val frameworkBaseName = project.name.split('-').joinToString("") { part ->
- part.replaceFirstChar(Char::uppercase)
- }
+ val frameworkBaseName = project.name
+ .split('-')
+ .joinToString("") { part ->
+ part.replaceFirstChar { it.titlecase(Locale.ROOT) }
+ }Add 🤖 Prompt for AI Agents |
||
| iosTargets.mapNotNull { | ||
| when (it) { | ||
| "iosX64" -> iosX64() | ||
| "iosArm64" -> iosArm64() | ||
| "iosSimulatorArm64" -> iosSimulatorArm64() | ||
| else -> null | ||
| } | ||
| }.forEach { iosTarget -> | ||
|
Comment on lines
+149
to
+156
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
- iosTargets.mapNotNull {
- when (it) {
- "iosX64" -> iosX64()
- "iosArm64" -> iosArm64()
- "iosSimulatorArm64" -> iosSimulatorArm64()
- else -> null
- }
- }.forEach { iosTarget ->
+ listOf(
+ iosX64(),
+ iosArm64(),
+ iosSimulatorArm64(),
+ ).forEach { iosTarget ->This removes the undeclared symbol while retaining identical behaviour. 🤖 Prompt for AI Agents |
||
| iosTarget.binaries.framework { | ||
| baseName = frameworkBaseName | ||
| isStatic = true | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| tasks { | ||
| findByName("test") ?: return@tasks | ||
| named<Test>("test") { | ||
|
|
@@ -174,3 +200,12 @@ subprojects { | |
| } | ||
| } | ||
| } | ||
|
|
||
| gradle.projectsEvaluated { | ||
| val xcFrameworkTask = project(":tasks-app-shared").tasks.findByName("embedAndSignAppleFrameworkForXcode") | ||
| val updateVersionTask = project(":tasks-app-ios").tasks.findByName("updateXcodeVersionConfig") | ||
|
|
||
| if (xcFrameworkTask != null && updateVersionTask != null) { | ||
| xcFrameworkTask.dependsOn(updateVersionTask) | ||
| } | ||
| } | ||
|
Comment on lines
+204
to
+211
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Task wiring OK Dependency ensures version config is generated before embedding the framework. Consider 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,13 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| plugins { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `kotlin-dsl` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repositories { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gradlePluginPortal() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mavenCentral() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| kotlin { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| jvmToolchain(17) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+10
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainToolchain configuration belongs once, not in every sub-project 🏁 Script executed: #!/bin/bash
rg "jvmToolchain" -n .Length of output: 600 Centralize JVM Toolchain Configuration The Affected files:
Suggested fix:
This will enforce a single source of truth for your JVM toolchain and avoid future divergence. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Pin the Kotlin version & expose the JVM toolchain at project level The script is succinct; a couple of improvements will future-proof it: plugins {
- `kotlin-dsl`
+ `kotlin-dsl` // uses the same Kotlin version as the running Gradle
}
kotlin {
- jvmToolchain(17)
+ jvmToolchain(17) // good ✔
}
+// Explicitly pin the Kotlin version to the one used in the catalog.
+// Avoids accidental bumps when Gradle upgrades its embedded Kotlin.
+dependencies {
+ implementation("org.jetbrains.kotlin:kotlin-stdlib:${libs.versions.kotlin.get()}")
+}This keeps 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Gradle/Konan cache limited to iOS only
The
actions/cachestep is currently gated byif: ${{ matrix.gradle_module == 'tasks-app-ios' }}.Android & Desktop builds therefore skip the cache entirely, which means no reuse of
.gradlenor Kotlin/Native artifacts for those jobs.If that was not a deliberate optimisation, drop the condition so every matrix entry benefits from caching (it is cheap on Ubuntu runners too).
🤖 Prompt for AI Agents