From dc4c15f5c515183c187ca7cf3a19b98e42d6bbab Mon Sep 17 00:00:00 2001 From: Trevor Lambert <78672774+trevor-lambert@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:55:40 -0600 Subject: [PATCH 1/5] feat: Capacitor 8 upgrade --- .github/workflows/ci.yml | 12 +++---- .github/workflows/docs-preview.yml | 4 +-- .github/workflows/docs-production.yml | 4 +-- .github/workflows/publish-android-old.yml | 4 +-- .github/workflows/publish-android.yml | 4 +-- .github/workflows/ui-tests.yml | 4 +-- IonicPortals/build.gradle.kts | 17 +++++---- .../kotlin/io/ionic/portals/PortalManager.kt | 4 +-- .../kotlin/io/ionic/portals/PortalView.kt | 3 +- README.md | 2 +- TestApp/build.gradle.kts | 28 ++++++++++----- TestAppCompose/build.gradle.kts | 35 ++++++++++++------- build.gradle.kts | 6 ++-- gradle/wrapper/gradle-wrapper.properties | 3 +- settings.gradle | 8 +++-- 15 files changed, 81 insertions(+), 57 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8395b42..7972fe4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,9 +18,9 @@ jobs: with: access_token: ${{ secrets.GITHUB_TOKEN }} - name: Get Latest - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: - node-version: 14.x + node-version: 22.x - uses: actions/checkout@v2 - name: Restore Dependency Cache uses: actions/cache@v1 @@ -33,10 +33,10 @@ jobs: with: path: core/ - name: set up JDK 17 - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: '17' - distribution: 'adopt' + distribution: 'temurin' verify-android: runs-on: ubuntu-latest timeout-minutes: 30 @@ -49,9 +49,9 @@ jobs: PORTALS_KEY: ${{ secrets.portals_key }} run: echo portals_key=\"$PORTALS_KEY\" > ./local.properties - name: set up JDK 17 - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: '17' - distribution: 'adopt' + distribution: 'temurin' - run: npm run verify working-directory: ./ \ No newline at end of file diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml index 412ae2f..2e64e60 100644 --- a/.github/workflows/docs-preview.yml +++ b/.github/workflows/docs-preview.yml @@ -27,10 +27,10 @@ jobs: PORTALS_KEY: ${{ secrets.portals_key }} run: echo portals_key=\"$PORTALS_KEY\" > ./local.properties - name: set up JDK 17 - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: '17' - distribution: 'adopt' + distribution: 'temurin' - name: Grant execute permission for gradlew run: chmod +x ./gradlew - name: Generate Docs diff --git a/.github/workflows/docs-production.yml b/.github/workflows/docs-production.yml index 760417b..342a706 100644 --- a/.github/workflows/docs-production.yml +++ b/.github/workflows/docs-production.yml @@ -27,10 +27,10 @@ jobs: PORTALS_KEY: ${{ secrets.portals_key }} run: echo portals_key=\"$PORTALS_KEY\" > ./local.properties - name: set up JDK 17 - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: '17' - distribution: 'adopt' + distribution: 'temurin' - name: Grant execute permission for gradlew run: chmod +x ./gradlew - name: Generate Docs diff --git a/.github/workflows/publish-android-old.yml b/.github/workflows/publish-android-old.yml index 05eead1..93f106c 100644 --- a/.github/workflows/publish-android-old.yml +++ b/.github/workflows/publish-android-old.yml @@ -13,10 +13,10 @@ jobs: PORTALS_KEY: ${{ secrets.portals_key }} run: echo portals_key=\"$PORTALS_KEY\" > ./local.properties - name: set up JDK 17 - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: '17' - distribution: 'adopt' + distribution: 'temurin' - name: Grant execute permission for gradlew run: chmod +x ./gradlew - name: Grant execute permission for publishing script diff --git a/.github/workflows/publish-android.yml b/.github/workflows/publish-android.yml index b48cac8..bfc6d05 100644 --- a/.github/workflows/publish-android.yml +++ b/.github/workflows/publish-android.yml @@ -12,10 +12,10 @@ jobs: steps: - uses: actions/checkout@v2 - name: set up JDK 17 - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: '17' - distribution: 'adopt' + distribution: 'temurin' - name: Grant execute permission for gradlew run: chmod +x ./gradlew - name: Grant execute permission for publishing script diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index b4a3baf..c12b0e9 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -26,10 +26,10 @@ jobs: run: echo portals_key=\"$PORTALS_KEY\" > ./local.properties - name: set up JDK 17 - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: '17' - distribution: 'adopt' + distribution: 'temurin' - name: Grant execute permission for gradlew run: chmod +x ./gradlew diff --git a/IonicPortals/build.gradle.kts b/IonicPortals/build.gradle.kts index 9d9ce2c..119a5e6 100644 --- a/IonicPortals/build.gradle.kts +++ b/IonicPortals/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { id("com.android.library") id("maven-publish") @@ -10,10 +12,10 @@ if (System.getenv("PORTALS_PUBLISH") == "true") { android { namespace = "io.ionic.portals" - compileSdk = 35 + compileSdk = 36 defaultConfig { - minSdk = 23 + minSdk = 24 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -33,18 +35,21 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" - } publishing { singleVariant("release") } } +kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_17 + } +} + dependencies { implementation(kotlin("reflect")) - api("com.capacitorjs:core:[7.0.0,7.1.0)") + api("com.capacitorjs:core:[8.0.0,9.0.0)") compileOnly("io.ionic:liveupdates:0.5.5") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") diff --git a/IonicPortals/src/main/kotlin/io/ionic/portals/PortalManager.kt b/IonicPortals/src/main/kotlin/io/ionic/portals/PortalManager.kt index 892b635..58a2969 100644 --- a/IonicPortals/src/main/kotlin/io/ionic/portals/PortalManager.kt +++ b/IonicPortals/src/main/kotlin/io/ionic/portals/PortalManager.kt @@ -202,7 +202,7 @@ object PortalManager { val rsaSignature = Signature.getInstance("SHA256withRSA") rsaSignature.initVerify(pubKey) rsaSignature.update(header) - rsaSignature.update(jwtDelimiter.toByte()) + rsaSignature.update(jwtDelimiter.code.toByte()) rsaSignature.update(payload) val result = rsaSignature.verify(tokenSignature) @@ -215,7 +215,7 @@ object PortalManager { registrationError() false } - } catch (e: Exception) { + } catch (_: Exception) { registrationError() } diff --git a/IonicPortals/src/main/kotlin/io/ionic/portals/PortalView.kt b/IonicPortals/src/main/kotlin/io/ionic/portals/PortalView.kt index ffac742..6121816 100644 --- a/IonicPortals/src/main/kotlin/io/ionic/portals/PortalView.kt +++ b/IonicPortals/src/main/kotlin/io/ionic/portals/PortalView.kt @@ -193,7 +193,6 @@ class PortalView : FrameLayout { portalFragment?.onInflate(context, attributeSet, null) } - val handler = Handler() val runnable = Runnable { val thisView = findViewById(id) if(thisView != null) { @@ -206,7 +205,7 @@ class PortalView : FrameLayout { } } - handler.post(runnable) + post(runnable) } } diff --git a/README.md b/README.md index da491d1..0eeceef 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Ionic Portals is a solution that lets you add web-based experiences to your nati The test projects within the repository will only work with a valid Portals key. Add the following new line to the `local.properties` file in the project root and enter your Portals key. ``` -portals_key="" +portals_key=YOUR_PORTALS_KEY ``` Note: This file is in the `.gitignore` and is not committed to repos by default. \ No newline at end of file diff --git a/TestApp/build.gradle.kts b/TestApp/build.gradle.kts index cd7bfdd..886004a 100644 --- a/TestApp/build.gradle.kts +++ b/TestApp/build.gradle.kts @@ -1,4 +1,5 @@ import org.jetbrains.kotlin.konan.properties.Properties +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import java.io.FileInputStream import com.android.build.api.variant.BuildConfigField @@ -9,7 +10,7 @@ plugins { android { namespace = "io.ionic.portals.testapp" - compileSdk = 35 + compileSdk = 36 buildFeatures { buildConfig = true @@ -18,7 +19,7 @@ android { defaultConfig { applicationId = "io.ionic.portals.testapp" minSdk = 24 - targetSdk = 35 + targetSdk = 36 versionCode = 1 versionName = "1.0" @@ -33,18 +34,20 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } +} - kotlinOptions { - jvmTarget = "1.8" +kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_17 } } androidComponents { onVariants { - it.buildConfigFields.put("PORTALS_KEY", BuildConfigField("String", getPortalsKey(), "portals registration key")) + it.buildConfigFields?.put("PORTALS_KEY", BuildConfigField("String", getPortalsKey(), "portals registration key")) } } @@ -67,5 +70,12 @@ fun getPortalsKey(): String { val propFile = rootProject.file("local.properties") val properties = Properties() properties.load(FileInputStream(propFile)) - return properties.getProperty("portals_key") ?: "" -} \ No newline at end of file + val raw = properties.getProperty("portals_key") ?: "" + val normalized = if (raw.length >= 2 && raw.first() == '"' && raw.last() == '"') { + raw.substring(1, raw.length - 1) + } else { + raw + } + val escaped = normalized.replace("\\", "\\\\").replace("\"", "\\\"") + return "\"$escaped\"" +} diff --git a/TestAppCompose/build.gradle.kts b/TestAppCompose/build.gradle.kts index 31b0946..58535ca 100644 --- a/TestAppCompose/build.gradle.kts +++ b/TestAppCompose/build.gradle.kts @@ -1,15 +1,17 @@ import org.jetbrains.kotlin.konan.properties.Properties +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import java.io.FileInputStream import com.android.build.api.variant.BuildConfigField plugins { id("com.android.application") id("org.jetbrains.kotlin.android") + id("org.jetbrains.kotlin.plugin.compose") } android { namespace = "io.ionic.portals.composetestapp" - compileSdk = 35 + compileSdk = 36 buildFeatures { buildConfig = true @@ -18,7 +20,7 @@ android { defaultConfig { applicationId = "io.ionic.portals.composetestapp" minSdk = 24 - targetSdk = 35 + targetSdk = 36 versionCode = 1 versionName = "1.0" @@ -35,18 +37,12 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = "1.8" + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } buildFeatures { compose = true } - composeOptions { - kotlinCompilerExtensionVersion = "1.5.15" - } packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" @@ -54,9 +50,15 @@ android { } } +kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_17 + } +} + androidComponents { onVariants { - it.buildConfigFields.put("PORTALS_KEY", + it.buildConfigFields?.put("PORTALS_KEY", BuildConfigField("String", getPortalsKey(), "portals registration key") ) } @@ -87,5 +89,12 @@ fun getPortalsKey(): String { val propFile = rootProject.file("local.properties") val properties = Properties() properties.load(FileInputStream(propFile)) - return properties.getProperty("portals_key") ?: "" -} \ No newline at end of file + val raw = properties.getProperty("portals_key") ?: "" + val normalized = if (raw.length >= 2 && raw.first() == '"' && raw.last() == '"') { + raw.substring(1, raw.length - 1) + } else { + raw + } + val escaped = normalized.replace("\\", "\\\\").replace("\"", "\\\"") + return "\"$escaped\"" +} diff --git a/build.gradle.kts b/build.gradle.kts index 5313e01..8cea39c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,11 +4,11 @@ import org.jetbrains.dokka.base.DokkaBaseConfiguration plugins { id("org.jetbrains.dokka") version "1.7.20" - id("org.jetbrains.kotlin.plugin.serialization") version "1.9.25" + id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20" } buildscript { - val kotlinVersion = "1.9.25" + val kotlinVersion = "2.2.20" extra.apply { set("kotlinVersion", kotlinVersion) } @@ -27,7 +27,7 @@ buildscript { } classpath("org.jetbrains.dokka:dokka-base:1.7.20") - classpath("com.android.tools.build:gradle:8.7.3") + classpath("com.android.tools.build:gradle:8.13.0") classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3037955..e69d040 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Mon May 08 10:58:01 CDT 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index 93715b0..360dcf8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,9 +5,11 @@ pluginManagement { mavenCentral() } plugins { - id 'org.jetbrains.kotlin.plugin.serialization' version "1.8.21" - id 'com.android.application' version '8.2.0' - id 'org.jetbrains.kotlin.android' version '1.8.21' + id 'org.jetbrains.kotlin.plugin.serialization' version "2.2.20" + id 'org.jetbrains.kotlin.plugin.compose' version "2.2.20" + id 'com.android.application' version '8.13.0' + id 'com.android.library' version '8.13.0' + id 'org.jetbrains.kotlin.android' version '2.2.20' } } From f86caf237ce9189fd6ab43141fc114e4cd69b163 Mon Sep 17 00:00:00 2001 From: Trevor Lambert <78672774+trevor-lambert@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:57:32 -0600 Subject: [PATCH 2/5] chore: upgrade actions/cache --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7972fe4..938c54e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: node-version: 22.x - uses: actions/checkout@v2 - name: Restore Dependency Cache - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/.npm key: ${{ runner.OS }}-dependency-cache-${{ hashFiles('**/package.json') }} From 4e4886c7162ba2dc9ee346af6507dea4c1ebb638 Mon Sep 17 00:00:00 2001 From: Trevor Lambert <78672774+trevor-lambert@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:09:44 -0600 Subject: [PATCH 3/5] chore(docs): update Dokka config for Kotlin 2 compatibility --- build.gradle.kts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 8cea39c..65a86fb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,5 @@ -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.gradle.DokkaTask -import org.jetbrains.dokka.base.DokkaBaseConfiguration - plugins { - id("org.jetbrains.dokka") version "1.7.20" + id("org.jetbrains.dokka") version "2.0.0" id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20" } @@ -26,7 +22,6 @@ buildscript { classpath("io.github.gradle-nexus:publish-plugin:1.1.0") } - classpath("org.jetbrains.dokka:dokka-base:1.7.20") classpath("com.android.tools.build:gradle:8.13.0") classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") @@ -46,9 +41,11 @@ allprojects { apply(plugin = "org.jetbrains.dokka") - tasks.withType().configureEach { - pluginConfiguration { - footerMessage = "Created by Ionic" + extensions.configure { + pluginsConfiguration { + html { + footerMessage.set("Created by Ionic") + } } } } @@ -56,4 +53,4 @@ allprojects { // register Clean task tasks.register("clean").configure { delete("build") -} \ No newline at end of file +} From bfcb3cfd7281651188b2c6295f511108708cb158 Mon Sep 17 00:00:00 2001 From: Trevor Lambert <78672774+trevor-lambert@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:17:01 -0600 Subject: [PATCH 4/5] chore(docs): fix ci --- build.gradle.kts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 65a86fb..6e199b8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,14 +40,6 @@ allprojects { } apply(plugin = "org.jetbrains.dokka") - - extensions.configure { - pluginsConfiguration { - html { - footerMessage.set("Created by Ionic") - } - } - } } // register Clean task From 2e89ed333eeffadc9ff4393f7c60c5211d4924ac Mon Sep 17 00:00:00 2001 From: Trevor Lambert <78672774+trevor-lambert@users.noreply.github.com> Date: Fri, 6 Mar 2026 13:55:22 -0600 Subject: [PATCH 5/5] test: stabilize invalid key dialog assertions in device farm --- .../ionic/portals/testapp/UnregisteredTests.kt | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/TestApp/src/androidTest/java/io/ionic/portals/testapp/UnregisteredTests.kt b/TestApp/src/androidTest/java/io/ionic/portals/testapp/UnregisteredTests.kt index 70e27e2..65b794a 100644 --- a/TestApp/src/androidTest/java/io/ionic/portals/testapp/UnregisteredTests.kt +++ b/TestApp/src/androidTest/java/io/ionic/portals/testapp/UnregisteredTests.kt @@ -1,12 +1,12 @@ package io.ionic.portals.testapp -import android.content.Intent import androidx.test.core.app.ActivityScenario import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.espresso.matcher.RootMatchers.isDialog import androidx.test.ext.junit.runners.AndroidJUnit4 import io.ionic.portals.PortalManager import org.junit.Before @@ -21,17 +21,12 @@ class UnregisteredTests { @Before fun setUp() { - scenario = ActivityScenario.launch(MainActivity::class.java).onActivity { activity -> - activity.sendBroadcast( - Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) - ) - }; + scenario = ActivityScenario.launch(MainActivity::class.java) } @Test fun when_portals_is_not_registered__display_unregistered_view() { - // Dismiss the invalid key dialog when it is displayed - onView(withText("OK")).perform(click()) + onView(withText("OK")).inRoot(isDialog()).perform(click()) // Verify that the unregistered view is displayed onView(withText(io.ionic.portals.R.string.unregistered_text)).check(matches(isDisplayed())) @@ -39,8 +34,9 @@ class UnregisteredTests { @Test fun when_portals_is_registered_with_bad_key__display_error_dialog() { - // Recreate the activity to trigger the dialog - onView(withText(io.ionic.portals.R.string.invalid_portals_key)).check(matches(isDisplayed())) + onView(withText(io.ionic.portals.R.string.invalid_portals_key)) + .inRoot(isDialog()) + .check(matches(isDisplayed())) } companion object { @@ -50,4 +46,4 @@ class UnregisteredTests { PortalManager.register("this is a bad key") } } -} \ No newline at end of file +}