Scaffold native Android build and enforce Rust boundaries (#89)
Dependency security audit / rustsec (push) Failing after 22s

This commit is contained in:
hugo committed 2026-10-08 10:16:51 +02:00
1 parent 30d9119302
commit e079b82b15
62 files changed
+5822

No files matched your search

@@ -0,0 +1,32 @@
package de.rfc1437.ironstorage
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.isHeading
import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import de.rfc1437.ironstorage.core.ShellFixtures
import de.rfc1437.ironstorage.core.ShellState
import de.rfc1437.ironstorage.core.ShellTab
import org.junit.Rule
import org.junit.Test
class ShellScreenTest {
@get:Rule val compose = createAndroidComposeRule<MainActivity>()
@Test
fun allRustFixtureStatesRender() {
ShellState.entries.forEach { state ->
compose.onNodeWithText("Fixture: ${state.name}").performScrollTo().performClick()
ShellTab.entries.forEach { tab ->
compose.onNode(
hasText(tab.name) and androidx.compose.ui.test.isSelectable()
).performClick()
val page = ShellFixtures.all.getValue(state).first { it.tab == tab }
compose.onNode(hasText(page.stateTitle) and isHeading()).assertIsDisplayed()
}
}
}
}
@@ -0,0 +1,39 @@
package de.rfc1437.ironstorage
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import de.rfc1437.ironstorage.core.ShellFixtures
import de.rfc1437.ironstorage.core.ShellRepository
import de.rfc1437.ironstorage.core.ShellSnapshot
import de.rfc1437.ironstorage.core.ShellState
import de.rfc1437.ironstorage.core.ShellTab
import javax.inject.Singleton
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class DebugShellRepository : ShellRepository {
private val mutableSnapshot =
MutableStateFlow(
ShellSnapshot(ShellState.Empty, ShellFixtures.all.getValue(ShellState.Empty))
)
override val snapshot: StateFlow<ShellSnapshot> = mutableSnapshot
override fun selectTab(tab: ShellTab) {
mutableSnapshot.value = mutableSnapshot.value.copy(selectedTab = tab)
}
fun showFixture(state: ShellState) {
mutableSnapshot.value =
mutableSnapshot.value.copy(state = state, pages = ShellFixtures.all.getValue(state))
}
}
@Module
@InstallIn(SingletonComponent::class)
object DebugShellModule {
@Provides
@Singleton
fun shellRepository(): ShellRepository = DebugShellRepository()
}
@@ -0,0 +1,29 @@
package de.rfc1437.ironstorage
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import de.rfc1437.ironstorage.core.ShellRepository
import de.rfc1437.ironstorage.core.ShellState
import de.rfc1437.ironstorage.features.ShellScreen
import de.rfc1437.ironstorage.features.ShellViewModel
@Composable
fun VariantContent(viewModel: ShellViewModel, repository: ShellRepository) {
Column(Modifier.statusBarsPadding()) {
Row(Modifier.horizontalScroll(rememberScrollState())) {
ShellState.entries.forEach { state ->
TextButton(onClick = { (repository as DebugShellRepository).showFixture(state) }) {
Text("Fixture: ${state.name}")
}
}
}
ShellScreen(viewModel)
}
}
+19
View File
@@ -0,0 +1,19 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name=".IronStorageApplication"
android:allowBackup="false"
android:fullBackupContent="@xml/backup_rules"
android:dataExtractionRules="@xml/data_extraction_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.IronStorage"
android:usesCleartextTraffic="false">
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,7 @@
package de.rfc1437.ironstorage
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class IronStorageApplication : Application()
@@ -0,0 +1,29 @@
package de.rfc1437.ironstorage
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.material3.MaterialTheme
import dagger.hilt.android.AndroidEntryPoint
import de.rfc1437.ironstorage.core.ShellRepository
import de.rfc1437.ironstorage.features.ShellViewModel
import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject lateinit var repository: ShellRepository
private val viewModel: ShellViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MaterialTheme {
VariantContent(viewModel, repository)
}
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

@@ -0,0 +1,3 @@
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_vault"
android:inset="18dp" />
@@ -0,0 +1,8 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp" android:height="108dp"
android:viewportWidth="108" android:viewportHeight="108">
<path android:fillColor="#00000000" android:strokeColor="#FFFFFFFF"
android:strokeWidth="3" android:pathData="M54,24A30,30 0,1 1,54 84A30,30 0,1 1,54 24ZM54,31A23,23 0,1 1,54 77A23,23 0,1 1,54 31ZM54,41A13,13 0,1 1,54 67A13,13 0,1 1,54 41" />
<path android:fillColor="#FFFFFFFF"
android:pathData="M52,41L56,41L56,52L66,58L64,62L54,56L44,62L42,58L52,52ZM50,50A5.7,5.7 0,1 1,58 58A5.7,5.7 0,1 1,50 50ZM79,37L85,37L85,71L79,71ZM73,39L79,39L79,44L73,44ZM73,64L79,64L79,69L73,69" />
</vector>
@@ -0,0 +1,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/icon_background" />
<foreground android:drawable="@drawable/ic_vault_foreground" />
<monochrome android:drawable="@drawable/ic_vault_monochrome" />
</adaptive-icon>
@@ -0,0 +1,3 @@
<resources>
<color name="icon_background">#30363F</color>
</resources>
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">IronStorage</string>
</resources>
@@ -0,0 +1,7 @@
<resources>
<style name="Theme.IronStorage" parent="android:style/Theme.Material.Light.NoActionBar">
<item name="android:windowLightStatusBar">false</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowActionModeOverlay">true</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<full-backup-content>
<exclude domain="root" path="." />
<exclude domain="file" path="." />
<exclude domain="database" path="." />
<exclude domain="sharedpref" path="." />
<exclude domain="external" path="." />
</full-backup-content>
@@ -0,0 +1,16 @@
<data-extraction-rules>
<cloud-backup>
<exclude domain="root" path="." />
<exclude domain="file" path="." />
<exclude domain="database" path="." />
<exclude domain="sharedpref" path="." />
<exclude domain="external" path="." />
</cloud-backup>
<device-transfer>
<exclude domain="root" path="." />
<exclude domain="file" path="." />
<exclude domain="database" path="." />
<exclude domain="sharedpref" path="." />
<exclude domain="external" path="." />
</device-transfer>
</data-extraction-rules>
@@ -0,0 +1,30 @@
package de.rfc1437.ironstorage
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import de.rfc1437.ironstorage.core.ShellRepository
import de.rfc1437.ironstorage.core.ShellSnapshot
import de.rfc1437.ironstorage.core.ShellState
import de.rfc1437.ironstorage.core.ShellTab
import javax.inject.Singleton
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class ScaffoldShellRepository : ShellRepository {
private val mutableSnapshot = MutableStateFlow(ShellSnapshot(ShellState.Empty, emptyList()))
override val snapshot: StateFlow<ShellSnapshot> = mutableSnapshot
override fun selectTab(tab: ShellTab) {
mutableSnapshot.value = mutableSnapshot.value.copy(selectedTab = tab)
}
}
@Module
@InstallIn(SingletonComponent::class)
object ReleaseShellModule {
@Provides
@Singleton
fun shellRepository(): ShellRepository = ScaffoldShellRepository()
}
@@ -0,0 +1,12 @@
package de.rfc1437.ironstorage
import androidx.compose.runtime.Composable
import de.rfc1437.ironstorage.core.ShellRepository
import de.rfc1437.ironstorage.features.ShellScreen
import de.rfc1437.ironstorage.features.ShellViewModel
@Composable
@Suppress("UNUSED_PARAMETER")
fun VariantContent(viewModel: ShellViewModel, repository: ShellRepository) {
ShellScreen(viewModel)
}
@@ -0,0 +1,20 @@
package de.rfc1437.ironstorage
import de.rfc1437.ironstorage.core.ShellState
import de.rfc1437.ironstorage.core.ShellTab
import org.junit.Assert.assertEquals
import org.junit.Test
class DebugShellRepositoryTest {
@Test
fun everyFixtureHasFivePagesAndSelectionSurvivesStateChanges() {
val repository = DebugShellRepository()
repository.selectTab(ShellTab.Totp)
ShellState.entries.forEach { state ->
repository.showFixture(state)
assertEquals(state, repository.snapshot.value.state)
assertEquals(ShellTab.entries, repository.snapshot.value.pages.map { it.tab })
assertEquals(ShellTab.Totp, repository.snapshot.value.selectedTab)
}
}
}