feat(android): replace WebView with tested native Compose API slice

Add real native onboarding/auth/dashboard/tasks/notes/shopping, origin-bound HTTPS and encrypted AndroidKeyStore session persistence. Archive the superseded browser renderer and tests. Verify 10 JVM tests, 6 native API-35 emulator tests, lintDebug/lintRelease and debug/unsigned release APK/AAB builds; the real upstream suite passes 7 tests and exports a nonblank native fixture screenshot. Tink host-tool update removes the old generated-protobuf warning. Full module/visual parity remains explicitly incomplete.
This commit is contained in:
Yuvomi Mobile Contributors committed 2026-10-21 09:12:47 +02:00
1 parent 81973c3c45
commit 8c480ddca3
28 files changed
+2628 -742

No files matched your search

@@ -0,0 +1,66 @@
package community.yuvomi.mobile
import android.content.Context
import android.net.Uri
import android.view.View
import android.view.ViewGroup
import android.webkit.WebResourceRequest
import android.webkit.WebView
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class BrowserSecurityTest {
@Test fun blocksCrossOriginPostAndSubresourceBeforeNetwork() {
val context = ApplicationProvider.getApplicationContext<Context>()
context.getSharedPreferences("server", Context.MODE_PRIVATE).edit()
.putString("origin", "https://family.example").commit()
ActivityScenario.launch(MainActivity::class.java).use { scenario ->
lateinit var web: WebView
lateinit var client: android.webkit.WebViewClient
scenario.onActivity {
web = requireNotNull(findWeb(it.window.decorView))
client = web.webViewClient
}
// Interception runs off the UI thread in WebView. POST navigations do
// not reliably call shouldOverrideUrlLoading, so this is a separate gate.
for (mainFrame in listOf(true, false)) {
val response = client.shouldInterceptRequest(
web,
Request("https://evil.example/collect", mainFrame),
)
assertNotNull(response)
assertEquals(403, response!!.statusCode)
}
assertNull(
client.shouldInterceptRequest(web, Request("https://family.example/tasks", true)),
)
}
}
private fun findWeb(view: View): WebView? {
if (view is WebView) return view
if (view is ViewGroup) {
for (i in 0 until view.childCount) {
findWeb(view.getChildAt(i))?.let {
return it
}
}
}
return null
}
private class Request(private val value: String, private val mainFrame: Boolean) : WebResourceRequest {
override fun getUrl(): Uri = Uri.parse(value)
override fun isForMainFrame() = mainFrame
override fun isRedirect() = false
override fun hasGesture() = true
override fun getMethod() = "POST"
override fun getRequestHeaders(): MutableMap<String, String> = mutableMapOf()
}
}