diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index f326e50..7d3a58b 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -18,7 +18,7 @@ val localProperties = Properties().apply { val releaseStoreFile: String? = localProperties.getProperty("kvitt.release.storeFile") // D-121: Kvitt's public host name (SPEC §8.5, §10.6), as BuildConfig.PUBLIC_HOST, the manifest placeholder publicHost -// (the https invite links of AC-2.6, port 8443) and RELEASE_SERVER_URL = https://:8443 (AC-2.7). From +// (the https invite links of AC-2.6, default port 443, D-124) and RELEASE_SERVER_URL = https:// (AC-2.7). From // -Pkvitt.publicHost (infra/publish-apk.sh passes it, from KVITT_PUBLIC_HOST in infra/pi/.env), else the key // kvitt.publicHost in android/local.properties (the owner sets it there; never committed), else the placeholder // kvitt.example.invalid (RFC 2606: never resolves), so a release built without it cannot talk to a real host. Debug @@ -32,9 +32,31 @@ require(Regex("[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+" "kvitt.publicHost must be a host name such as kvitt.duckdns.org (no scheme, port or path), was '$publicHost'" } -// SPEC §10.5.3: the pinned Caddy root replaces the system anchors for icehack.asuscomm.com only when this is true. +// SPEC §10.5.3 (D-082): the pinned NPM root (D-120) replaces the system anchors for kvitt.publicHost only when this is true. val pinnedRoot = providers.gradleProperty("kvitt.tls.pinnedRoot").orElse("false").get().toBoolean() +/** + * Writes res/xml/network_security_config.xml from [template] with `@PUBLIC_HOST@` replaced by [host] (D-121). The + * fallback is not prepared (§10.5.3): without the committed root [rootCa] the build stops here with that message. + */ +abstract class PinnedRootConfigTask : DefaultTask() { + @get:InputFile @get:PathSensitive(PathSensitivity.NONE) abstract val template: RegularFileProperty + @get:Input abstract val host: Property + @get:Internal abstract val rootCa: RegularFileProperty + @get:OutputDirectory abstract val outputDir: DirectoryProperty + + @TaskAction + fun write() { + val ca = rootCa.get().asFile + if (!ca.isFile) throw GradleException("kvitt.tls.pinnedRoot=true needs the pinned root ${ca.path} (SPEC §10.5.3)") + val root = outputDir.get().asFile + root.deleteRecursively() + val xml = root.resolve("xml/network_security_config.xml") + xml.parentFile.mkdirs() + xml.writeText(template.get().asFile.readText().replace("@PUBLIC_HOST@", host.get())) + } +} + android { namespace = "se.hagstedt.kvitt" defaultConfig { @@ -77,10 +99,20 @@ android { proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } } - sourceSets { - // The pinned-root network security config (and the committed kvitt_root_ca.crt in src/release/res/raw) only - // when kvitt.tls.pinnedRoot=true; otherwise src/main's system-anchors config applies. - if (pinnedRoot) getByName("release").res.directories.add("src/releasePinnedRoot/res") +} + +// The pinned-root network security config (generated from src/releasePinnedRoot, trusting src/release/res/raw/ +// kvitt_root_ca.crt) only in release builds and only when kvitt.tls.pinnedRoot=true; otherwise src/main's +// system-anchors config applies. +if (pinnedRoot) { + val pinnedRootConfig = tasks.register("generatePinnedRootNetworkSecurityConfig", PinnedRootConfigTask::class.java) { + description = "Writes the pinned-root network security config for kvitt.publicHost (SPEC §10.5.3)." + template.set(layout.projectDirectory.file("src/releasePinnedRoot/network_security_config.xml")) + host.set(publicHost) + rootCa.set(layout.projectDirectory.file("src/release/res/raw/kvitt_root_ca.crt")) + } + androidComponents.onVariants(androidComponents.selector().withBuildType("release")) { variant -> + variant.sources.res?.addGeneratedSourceDirectory(pinnedRootConfig, PinnedRootConfigTask::outputDir) } } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index f183946..2433753 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -35,7 +35,8 @@ + (BuildConfig.PUBLIC_HOST, D-121); no port, since Kvitt is served on 443 (D-124). A filter without a + port matches every port of the host. --> @@ -43,7 +44,6 @@ diff --git a/android/app/src/main/kotlin/se/hagstedt/kvitt/link/AppLinks.kt b/android/app/src/main/kotlin/se/hagstedt/kvitt/link/AppLinks.kt index 9c2330f..3c09630 100644 --- a/android/app/src/main/kotlin/se/hagstedt/kvitt/link/AppLinks.kt +++ b/android/app/src/main/kotlin/se/hagstedt/kvitt/link/AppLinks.kt @@ -26,8 +26,8 @@ sealed interface AppLink { /** * Parses the intents `MainActivity` receives: `kvitt://invite?server=…&code=…[&from=…]` and - * `https://:8443/i/[?from=…]` (AC-2.6; the manifest's intent filter takes its host from the - * `publicHost` placeholder, D-121), both through `:feature:onboarding`'s [InviteLink], the reader S-1 uses for pasted + * `https:///i/[?from=…]` (AC-2.6, D-124; the manifest's intent filter takes its host from + * the `publicHost` placeholder, D-121), both through `:feature:onboarding`'s [InviteLink], the reader S-1 uses for pasted * links, which takes the server from the link itself, and the notification taps this app builds ([openIntent]). */ object AppLinks { diff --git a/android/app/src/main/kotlin/se/hagstedt/kvitt/link/ReleaseServer.kt b/android/app/src/main/kotlin/se/hagstedt/kvitt/link/ReleaseServer.kt index 4bd2ab0..7731ed2 100644 --- a/android/app/src/main/kotlin/se/hagstedt/kvitt/link/ReleaseServer.kt +++ b/android/app/src/main/kotlin/se/hagstedt/kvitt/link/ReleaseServer.kt @@ -4,8 +4,9 @@ import se.hagstedt.kvitt.BuildConfig import se.hagstedt.kvitt.feature.onboarding.releaseServerUrl /** - * The release server, `https://:8443` (AC-2.7, D-121): prefilled on S-1 and the host of the - * `https` invite links the manifest declares (AC-2.6, the `publicHost` placeholder). `PUBLIC_HOST` is the Gradle - * property `kvitt.publicHost`; without it the build uses the placeholder `kvitt.example.invalid`, which never resolves. + * The release server, `https://` on port 443 (AC-2.7, D-121, D-124): prefilled on S-1 and the + * host of the `https` invite links the manifest declares (AC-2.6, the `publicHost` placeholder). `PUBLIC_HOST` is the + * Gradle property `kvitt.publicHost`; without it the build uses the placeholder `kvitt.example.invalid`, which never + * resolves. */ val RELEASE_SERVER_URL: String = releaseServerUrl(BuildConfig.PUBLIC_HOST) diff --git a/android/app/src/releasePinnedRoot/res/xml/network_security_config.xml b/android/app/src/releasePinnedRoot/network_security_config.xml similarity index 51% rename from android/app/src/releasePinnedRoot/res/xml/network_security_config.xml rename to android/app/src/releasePinnedRoot/network_security_config.xml index 764bd77..36969db 100644 --- a/android/app/src/releasePinnedRoot/res/xml/network_security_config.xml +++ b/android/app/src/releasePinnedRoot/network_security_config.xml @@ -1,6 +1,8 @@ - + @@ -8,7 +10,7 @@ - icehack.asuscomm.com + @PUBLIC_HOST@ diff --git a/android/app/src/test/kotlin/se/hagstedt/kvitt/link/AppLinksTest.kt b/android/app/src/test/kotlin/se/hagstedt/kvitt/link/AppLinksTest.kt index 6941876..3bcf0c0 100644 --- a/android/app/src/test/kotlin/se/hagstedt/kvitt/link/AppLinksTest.kt +++ b/android/app/src/test/kotlin/se/hagstedt/kvitt/link/AppLinksTest.kt @@ -23,10 +23,11 @@ class AppLinksTest { private val host = BuildConfig.PUBLIC_HOST @Test - fun theReleaseServerIsHttpsOnPort8443OfThePublicHost() { - assertEquals("https://$host:8443", RELEASE_SERVER_URL) + fun theReleaseServerIsHttpsOnTheDefaultPortOfThePublicHost() { + // D-124: port 443, written without a port. + assertEquals("https://$host", RELEASE_SERVER_URL) // Without kvitt.publicHost the build uses the placeholder, which never resolves (RFC 2606). - assertEquals("https://kvitt.example.invalid:8443", releaseServerUrl("kvitt.example.invalid")) + assertEquals("https://kvitt.example.invalid", releaseServerUrl("kvitt.example.invalid")) } @Test @@ -37,8 +38,13 @@ class AppLinksTest { 0, ).any { it.activityInfo.name == "se.hagstedt.kvitt.MainActivity" } assertTrue(opens("$RELEASE_SERVER_URL/i/ABCDEFGHJKMN")) - assertFalse(opens("https://other.example.org:8443/i/ABCDEFGHJKMN")) - assertFalse(opens("https://$host/i/ABCDEFGHJKMN")) + assertTrue(opens("https://$host/i/ABCDEFGHJKMN?from=Jacob")) + assertFalse(opens("https://other.example.org/i/ABCDEFGHJKMN")) + assertFalse(opens("http://$host/i/ABCDEFGHJKMN")) + assertFalse(opens("https://$host/apk/latest")) + // A filter without a port matches every port of the host (android:port="443" would match no link without an + // explicit port), so an old :8443 link still opens S-1, with the server the link names (AC-2.6). + assertTrue(opens("https://$host:8443/i/ABCDEFGHJKMN")) } @Test @@ -50,13 +56,15 @@ class AppLinksTest { @Test fun appLinkNormalisesTheCode() { // §8.3: lowercase, O -> 0, I and L -> 1. - assertEquals("ABCD0113EFGH", AppLinks.parseUri("kvitt://invite?server=https%3A%2F%2F$host%3A8443&code=abcdo1l3efgh")?.code) + assertEquals("ABCD0113EFGH", AppLinks.parseUri("kvitt://invite?server=https%3A%2F%2F$host&code=abcdo1l3efgh")?.code) } @Test fun landingLinkFillsTheServerFromTheLink() { val link = AppLinks.parseUri("$RELEASE_SERVER_URL/i/ABCDEFGHJKMN") assertEquals(AppLink.Invite(RELEASE_SERVER_URL, "ABCDEFGHJKMN"), link) + // An explicit default port names the same server. + assertEquals(AppLink.Invite(RELEASE_SERVER_URL, "ABCDEFGHJKMN"), AppLinks.parseUri("https://$host:443/i/ABCDEFGHJKMN")) } @Test @@ -67,7 +75,7 @@ class AppLinksTest { ) assertEquals( AppLink.Invite(RELEASE_SERVER_URL, "ABCDEFGHJKMN", "Jacob"), - AppLinks.parseUri("kvitt://invite?server=https%3A%2F%2F${host.replace(".", "%2E")}%3A8443&code=ABCDEFGHJKMN&from=Jacob"), + AppLinks.parseUri("kvitt://invite?server=https%3A%2F%2F${host.replace(".", "%2E")}&code=ABCDEFGHJKMN&from=Jacob"), ) assertNull(AppLinks.parseUri("$RELEASE_SERVER_URL/i/ABCDEFGHJKMN")?.adminName) } diff --git a/android/feature/onboarding/src/main/kotlin/se/hagstedt/kvitt/feature/onboarding/InviteLink.kt b/android/feature/onboarding/src/main/kotlin/se/hagstedt/kvitt/feature/onboarding/InviteLink.kt index 71e6c4c..6320445 100644 --- a/android/feature/onboarding/src/main/kotlin/se/hagstedt/kvitt/feature/onboarding/InviteLink.kt +++ b/android/feature/onboarding/src/main/kotlin/se/hagstedt/kvitt/feature/onboarding/InviteLink.kt @@ -45,8 +45,10 @@ object InviteLink { val segments = path.trim('/').split('/') if (segments.size != 2 || segments[0] != "i") return null val code = code(segments[1]) ?: return null - val port = if (uri.port == -1) "" else ":${uri.port}" - return PastedInvite(code, "${uri.scheme.lowercase()}://${uri.host}$port", adminName(uri.rawQuery?.let(::params)?.get(FROM))) + val scheme = uri.scheme.lowercase() + // An explicit default port is dropped, so `https://:443/i/…` names the release server `https://`. + val port = if (uri.port == -1 || uri.port == DEFAULT_PORTS[scheme]) "" else ":${uri.port}" + return PastedInvite(code, "$scheme://${uri.host}$port", adminName(uri.rawQuery?.let(::params)?.get(FROM))) } /** The first value of each key; a value that is not valid percent-encoding is dropped. */ @@ -67,6 +69,7 @@ object InviteLink { } private const val FROM = "from" + private val DEFAULT_PORTS = mapOf("https" to 443, "http" to 80) /** The normalised code when [text] normalises to exactly 12 alphabet characters (§8.3), else null. */ private fun code(text: String): String? { diff --git a/android/feature/onboarding/src/main/kotlin/se/hagstedt/kvitt/feature/onboarding/ServerAddress.kt b/android/feature/onboarding/src/main/kotlin/se/hagstedt/kvitt/feature/onboarding/ServerAddress.kt index 1d7582b..faf6e3b 100644 --- a/android/feature/onboarding/src/main/kotlin/se/hagstedt/kvitt/feature/onboarding/ServerAddress.kt +++ b/android/feature/onboarding/src/main/kotlin/se/hagstedt/kvitt/feature/onboarding/ServerAddress.kt @@ -4,14 +4,12 @@ import java.net.URI import java.net.URISyntaxException import javax.inject.Qualifier -/** The release server's port: the router forwards external 8443 to the TLS proxy (SPEC §10.1, D-120). */ -const val RELEASE_SERVER_PORT = 8443 - /** - * The release server of a public host name, `https://:8443` (AC-2.7, D-121). `:app` builds it from its - * `BuildConfig.PUBLIC_HOST` (the Gradle property `kvitt.publicHost`) and provides it as [ReleaseServerUrl]. + * The release server of a public host name, `https://` on the default port 443, where NPM selects Kvitt by host + * name (AC-2.7, D-121, D-124). `:app` builds it from its `BuildConfig.PUBLIC_HOST` (the Gradle property + * `kvitt.publicHost`) and provides it as [ReleaseServerUrl]. */ -fun releaseServerUrl(publicHost: String): String = "https://$publicHost:$RELEASE_SERVER_PORT" +fun releaseServerUrl(publicHost: String): String = "https://$publicHost" /** The release server prefilled on S-1 (AC-2.7), `RELEASE_SERVER_URL` in `:app` (D-121). */ @Qualifier diff --git a/android/feature/onboarding/src/test/kotlin/se/hagstedt/kvitt/feature/onboarding/InviteLinkTest.kt b/android/feature/onboarding/src/test/kotlin/se/hagstedt/kvitt/feature/onboarding/InviteLinkTest.kt index 4101a98..096af90 100644 --- a/android/feature/onboarding/src/test/kotlin/se/hagstedt/kvitt/feature/onboarding/InviteLinkTest.kt +++ b/android/feature/onboarding/src/test/kotlin/se/hagstedt/kvitt/feature/onboarding/InviteLinkTest.kt @@ -24,7 +24,7 @@ class InviteLinkTest { @Test fun theAppLinkGivesCodeAndServer() { - val parsed = InviteLink.parse("kvitt://invite?server=https%3A%2F%2Fkvitt.example.org%3A8443&code=7K3MQX9D2HPA") + val parsed = InviteLink.parse("kvitt://invite?server=https%3A%2F%2Fkvitt.example.org&code=7K3MQX9D2HPA") assertEquals(PastedInvite("7K3MQX9D2HPA", TEST_RELEASE_SERVER_URL), parsed) assertNull(InviteLink.parse("kvitt://other?code=7K3MQX9D2HPA")) } @@ -33,21 +33,28 @@ class InviteLinkTest { fun linksMayNameTheAdmin_andABadNameIsDropped() { assertEquals( PastedInvite("7K3MQX9D2HPA", TEST_RELEASE_SERVER_URL, "Jacob"), - InviteLink.parse("kvitt://invite?server=https%3A%2F%2Fkvitt.example.org%3A8443&code=7K3MQX9D2HPA&from=Jacob"), + InviteLink.parse("kvitt://invite?server=https%3A%2F%2Fkvitt.example.org&code=7K3MQX9D2HPA&from=Jacob"), ) assertEquals( PastedInvite("7K3MQX9D2HPA", TEST_RELEASE_SERVER_URL, "Åsa Berg"), - InviteLink.parse("https://kvitt.example.org:8443/i/7K3MQX9D2HPA?from=%C3%85sa+Berg"), + InviteLink.parse("https://kvitt.example.org/i/7K3MQX9D2HPA?from=%C3%85sa+Berg"), ) for (bad in listOf("", "%20", "a%0Ab", "A".repeat(41))) { - assertEquals(bad, PastedInvite("7K3MQX9D2HPA", TEST_RELEASE_SERVER_URL), InviteLink.parse("https://kvitt.example.org:8443/i/7K3MQX9D2HPA?from=$bad")) + assertEquals(bad, PastedInvite("7K3MQX9D2HPA", TEST_RELEASE_SERVER_URL), InviteLink.parse("https://kvitt.example.org/i/7K3MQX9D2HPA?from=$bad")) } } @Test fun theLandingLinkGivesCodeAndServer() { - assertEquals(PastedInvite("7K3MQX9D2HPA", TEST_RELEASE_SERVER_URL), InviteLink.parse("https://kvitt.example.org:8443/i/7K3MQX9D2HPA")) + assertEquals(PastedInvite("7K3MQX9D2HPA", TEST_RELEASE_SERVER_URL), InviteLink.parse("https://kvitt.example.org/i/7K3MQX9D2HPA")) assertEquals(PastedInvite("7K3MQX9D2HPA", "http://192.168.1.210:8080"), InviteLink.parse("http://192.168.1.210:8080/i/7k3mqx9d2hpa")) - assertNull(InviteLink.parse("https://kvitt.example.org:8443/x/7K3MQX9D2HPA")) + assertNull(InviteLink.parse("https://kvitt.example.org/x/7K3MQX9D2HPA")) + } + + @Test + fun anExplicitDefaultPortIsDropped_andAnyOtherPortKept() { + assertEquals(PastedInvite("7K3MQX9D2HPA", TEST_RELEASE_SERVER_URL), InviteLink.parse("https://kvitt.example.org:443/i/7K3MQX9D2HPA")) + assertEquals(PastedInvite("7K3MQX9D2HPA", "http://192.168.1.210"), InviteLink.parse("http://192.168.1.210:80/i/7K3MQX9D2HPA")) + assertEquals(PastedInvite("7K3MQX9D2HPA", "https://kvitt.example.org:8443"), InviteLink.parse("https://kvitt.example.org:8443/i/7K3MQX9D2HPA")) } } diff --git a/android/feature/onboarding/src/test/kotlin/se/hagstedt/kvitt/feature/onboarding/PairingViewModelTest.kt b/android/feature/onboarding/src/test/kotlin/se/hagstedt/kvitt/feature/onboarding/PairingViewModelTest.kt index 02327a9..3d81328 100644 --- a/android/feature/onboarding/src/test/kotlin/se/hagstedt/kvitt/feature/onboarding/PairingViewModelTest.kt +++ b/android/feature/onboarding/src/test/kotlin/se/hagstedt/kvitt/feature/onboarding/PairingViewModelTest.kt @@ -169,7 +169,7 @@ class PairingViewModelTest { @Test fun pasteFillsCodeAndServerOrSaysTheCodeHasTwelveCharacters() { val (_, vm) = vm() - vm.onPaste("https://kvitt.example.org:8443/i/7K3MQX9D2HPA") + vm.onPaste("https://kvitt.example.org/i/7K3MQX9D2HPA") assertEquals("7K3MQX9D2HPA", vm.state.value.code) assertNull(vm.state.value.error) vm.onPaste("hello") diff --git a/android/feature/onboarding/src/test/kotlin/se/hagstedt/kvitt/feature/onboarding/ServerAddressTest.kt b/android/feature/onboarding/src/test/kotlin/se/hagstedt/kvitt/feature/onboarding/ServerAddressTest.kt index f649925..d647eed 100644 --- a/android/feature/onboarding/src/test/kotlin/se/hagstedt/kvitt/feature/onboarding/ServerAddressTest.kt +++ b/android/feature/onboarding/src/test/kotlin/se/hagstedt/kvitt/feature/onboarding/ServerAddressTest.kt @@ -42,10 +42,11 @@ class ServerAddressTest { } @Test - fun theReleaseServerIsHttpsOnPort8443OfThePublicHost() { - // D-121: the build's default kvitt.publicHost is the placeholder, which never resolves (RFC 2606). - assertEquals("https://kvitt.example.invalid:8443", releaseServerUrl("kvitt.example.invalid")) - assertEquals("https://kvitt.duckdns.org:8443", releaseServerUrl("kvitt.duckdns.org")) + fun theReleaseServerIsHttpsOnTheDefaultPortOfThePublicHost() { + // D-121: the build's default kvitt.publicHost is the placeholder, which never resolves (RFC 2606); D-124: port 443, + // written without a port. + assertEquals("https://kvitt.example.invalid", releaseServerUrl("kvitt.example.invalid")) + assertEquals("https://kvitt.duckdns.org", releaseServerUrl("kvitt.duckdns.org")) } @Test diff --git a/android/feature/onboarding/src/test/kotlin/se/hagstedt/kvitt/feature/onboarding/screenshot/OnboardingStates.kt b/android/feature/onboarding/src/test/kotlin/se/hagstedt/kvitt/feature/onboarding/screenshot/OnboardingStates.kt index 0e1e831..23380bd 100644 --- a/android/feature/onboarding/src/test/kotlin/se/hagstedt/kvitt/feature/onboarding/screenshot/OnboardingStates.kt +++ b/android/feature/onboarding/src/test/kotlin/se/hagstedt/kvitt/feature/onboarding/screenshot/OnboardingStates.kt @@ -27,9 +27,10 @@ private const val LAN_SERVER = "http://192.168.1.210:8080" /** * The release server the goldens show on S-1's server row. Fixed, not `:app`'s `BuildConfig.PUBLIC_HOST`, so a - * checkout's `kvitt.publicHost` never changes a render (D-121); the host is the one the goldens were recorded with. + * checkout's `kvitt.publicHost` never changes a render (D-121); the fixture host of the other modules, on port 443 + * (D-124). */ -private const val GOLDEN_SERVER = "https://icehack.asuscomm.com:8443" +private const val GOLDEN_SERVER = "https://kvitt.example.org" /** * Every state of S-1, S-2, S-3 and S-18 (design-spec §4 and PLAN-M7a §5). Each is produced by the screen's real diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_again_en_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_again_en_light.png index e842b80..7803d83 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_again_en_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_again_en_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_again_sv_dark.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_again_sv_dark.png index 362c87e..7545454 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_again_sv_dark.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_again_sv_dark.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_again_sv_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_again_sv_light.png index bbe2ac4..84d2d8d 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_again_sv_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_again_sv_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_empty_en_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_empty_en_light.png index 832d53f..c286cd9 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_empty_en_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_empty_en_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_empty_sv_dark.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_empty_sv_dark.png index 013c59f..2398313 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_empty_sv_dark.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_empty_sv_dark.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_empty_sv_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_empty_sv_light.png index a048880..a884b19 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_empty_sv_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_empty_sv_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_en_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_en_light.png index 3b920b4..f7fa09c 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_en_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_en_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_en_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_en_light.png index 2dff041..2d91dd6 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_en_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_en_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_network_en_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_network_en_light.png index 5acec58..5ef18c7 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_network_en_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_network_en_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_network_sv_dark.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_network_sv_dark.png index 8a6b505..77c493d 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_network_sv_dark.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_network_sv_dark.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_network_sv_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_network_sv_light.png index f318b3f..d0e9cba 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_network_sv_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_network_sv_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_rate_limited_en_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_rate_limited_en_light.png index a7890ed..5831902 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_rate_limited_en_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_rate_limited_en_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_rate_limited_sv_dark.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_rate_limited_sv_dark.png index 4da2478..2c49b75 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_rate_limited_sv_dark.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_rate_limited_sv_dark.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_rate_limited_sv_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_rate_limited_sv_light.png index a20222c..17df766 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_rate_limited_sv_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_rate_limited_sv_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_sv_dark.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_sv_dark.png index 8738a93..bde8487 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_sv_dark.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_sv_dark.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_sv_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_sv_light.png index d5f430b..e56acb3 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_error_sv_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_error_sv_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_link_admin_en_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_link_admin_en_light.png index 0abc13c..af979a0 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_link_admin_en_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_link_admin_en_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_link_admin_sv_dark.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_link_admin_sv_dark.png index babf387..3511cca 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_link_admin_sv_dark.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_link_admin_sv_dark.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_link_admin_sv_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_link_admin_sv_light.png index 05aa3f1..0df3bde 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_link_admin_sv_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_link_admin_sv_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_loading_en_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_loading_en_light.png index c35b52e..9755a40 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_loading_en_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_loading_en_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_loading_sv_dark.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_loading_sv_dark.png index 7ef82b5..b7d8ac1 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_loading_sv_dark.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_loading_sv_dark.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_loading_sv_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_loading_sv_light.png index b40cc35..0281bbf 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_loading_sv_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_loading_sv_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_offline_en_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_offline_en_light.png index 5acec58..5ef18c7 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_offline_en_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_offline_en_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_offline_sv_dark.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_offline_sv_dark.png index 8a6b505..77c493d 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_offline_sv_dark.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_offline_sv_dark.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_offline_sv_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_offline_sv_light.png index f318b3f..d0e9cba 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_offline_sv_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_offline_sv_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_sv_dark.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_sv_dark.png index 9785142..e550de1 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_sv_dark.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_sv_dark.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_sv_light.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_sv_light.png index f85fa92..eb175ac 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_sv_light.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_sv_light.png differ diff --git a/android/feature/onboarding/src/test/screenshots/s1_pairing_sv_light_200.png b/android/feature/onboarding/src/test/screenshots/s1_pairing_sv_light_200.png index 5ddd51d..38d1b8b 100644 Binary files a/android/feature/onboarding/src/test/screenshots/s1_pairing_sv_light_200.png and b/android/feature/onboarding/src/test/screenshots/s1_pairing_sv_light_200.png differ