Implement adaptive Android shell and safe navigation (#102)
Android ARM verification / rust (push) Failing after 47m11s
Android ARM verification / android (push) Failing after 8s
Dependency security audit / rustsec (push) Successful in 25s

This commit is contained in:
hugo committed 2026-10-09 20:33:31 +02:00
1 parent c7703d8efd
commit d22d71eb5e
32 files changed
+2167 -77

No files matched your search

+2
View File
@@ -86,6 +86,8 @@ documented in [`docs/android/presentation-security.md`](docs/android/presentatio
Name-only Android search and its state/security policy are documented in
[`docs/android/name-search.md`](docs/android/name-search.md).
Adaptive navigation, appearance, safe restoration and shell validation are documented in
[`docs/android/adaptive-shell.md`](docs/android/adaptive-shell.md).
## Project layout
@@ -0,0 +1,177 @@
package de.rfc1437.ironstorage
import android.content.pm.ActivityInfo
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.input.InputMode
import androidx.compose.ui.input.InputModeManager
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalInputModeManager
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.assertHeightIsAtLeast
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsSelected
import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performKeyInput
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performSemanticsAction
import androidx.compose.ui.test.pressKey
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModelProvider
import de.rfc1437.ironstorage.core.OnboardingRepository
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 de.rfc1437.ironstorage.features.ApplicationTheme
import de.rfc1437.ironstorage.features.SetupFormViewModel
import de.rfc1437.ironstorage.features.ShellScreen
import de.rfc1437.ironstorage.features.ShellViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
class AdaptiveShellTest {
@get:Rule val compose = createAndroidComposeRule<MainActivity>()
@Test fun independentPreferencesStackAndSafeRecreationUseCompactAndExpandedNavigation() {
val original = ViewModelProvider(compose.activity)[ShellViewModel::class.java]
val setup = ViewModelProvider(compose.activity)[SetupFormViewModel::class.java]
val repository = Fixture()
val saved = SavedStateHandle()
var setupCancellations = 0
val onboarding = object : OnboardingRepository by original.onboarding {
override fun cancel() {
setupCancellations += 1
original.onboarding.cancel()
}
}
val model =
ShellViewModel(
repository,
onboarding,
original.passwords,
original.home,
original.search,
original.authentication,
saved
)
compose.activity.runOnUiThread {
compose.activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
compose.waitUntil(10000) {
compose.activity.resources.configuration.orientation ==
android.content.res.Configuration.ORIENTATION_PORTRAIT
}
lateinit var inputMode: InputModeManager
fun show(viewModel: ShellViewModel, largeFont: Boolean = false) {
compose.activity.runOnUiThread {
compose.activity.setProtectedTestContent {
inputMode = LocalInputModeManager.current
val density = LocalDensity.current
CompositionLocalProvider(
LocalDensity provides Density(density.density, if (largeFont) 2f else 1f)
) {
ApplicationTheme(original.authentication) { ShellScreen(viewModel, setup) }
}
}
}
}
show(model)
compose.onNodeWithTag("shell-bottom-navigation").assertIsDisplayed()
compose.runOnIdle { assertTrue(inputMode.requestInputMode(InputMode.Keyboard)) }
compose.onNodeWithTag("shell-tab-Preferences")
.assertHasClickAction().assertHeightIsAtLeast(48.dp)
.performSemanticsAction(SemanticsActions.RequestFocus) { assertTrue(it()) }
.performKeyInput { pressKey(Key.Enter) }
compose.runOnIdle {
setup.passphrase = "public fixture passphrase"
setup.armor = "public fixture armor"
setup.token = "public fixture token"
}
compose.onNodeWithTag("shell-open-appearance").performClick()
compose.runOnIdle {
assertEquals("", setup.passphrase)
assertEquals("", setup.armor)
assertEquals("", setup.token)
assertEquals(1, setupCancellations)
}
compose.onNodeWithText("System default").assertIsDisplayed()
compose.runOnIdle { setup.token = "public fixture token" }
compose.onNodeWithTag("shell-tab-Search").performClick()
compose.runOnIdle {
assertEquals("", setup.token)
assertEquals(2, setupCancellations)
}
compose.onNodeWithTag("shell-tab-Preferences").performClick()
compose.onNodeWithText("System default").assertIsDisplayed()
compose.onNodeWithText("Back").performScrollTo().performClick()
compose.onNodeWithTag("shell-open-appearance").assertIsDisplayed()
compose.onNodeWithTag("shell-open-appearance").performClick()
compose.runOnIdle {
assertEquals("Preferences", saved.get<String>("shell.tab"))
assertEquals(true, saved.get<Boolean>("shell.appearance"))
assertTrue(
saved.keys().all {
saved.get<Any>(it) is String ||
saved.get<Any>(it) is Boolean
}
)
assertFalse(original.authentication.state.value.unlocked)
}
// A new ViewModel receives only primitive safe navigation values, as after process death.
val restored =
ShellViewModel(
repository,
onboarding,
original.passwords,
original.home,
original.search,
original.authentication,
SavedStateHandle(
saved.keys().associateWith {
saved.get<Any>(it)
}
)
)
show(restored)
compose.onNodeWithText("System default").assertIsDisplayed()
compose.onNodeWithTag("shell-tab-Preferences").assertIsSelected()
compose.activity.runOnUiThread {
compose.activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
compose.waitUntil(10000) {
compose.activity.resources.configuration.orientation ==
android.content.res.Configuration.ORIENTATION_LANDSCAPE
}
show(restored, largeFont = true)
compose.onNodeWithTag("shell-navigation-rail").assertIsDisplayed()
compose.onNodeWithTag("shell-tab-Preferences").assertIsSelected()
compose.onNodeWithText("Dark").performScrollTo().assertIsDisplayed()
compose.onNodeWithText("Back").performScrollTo().performClick()
compose.onNodeWithTag("shell-open-appearance").assertIsDisplayed()
}
private class Fixture : ShellRepository {
override val snapshot = MutableStateFlow(
ShellSnapshot(
ShellState.Locked,
ShellFixtures.all.getValue(ShellState.Locked)
)
)
override fun selectTab(tab: ShellTab) {
snapshot.value =
snapshot.value.copy(selectedTab = tab)
}
}
}
@@ -101,6 +101,9 @@ class AuthenticationScreenTest {
var unlocks = 0
var disabled = 0
var cancelled = 0
override fun setAppearance(appearance: de.rfc1437.ironstorage.core.Appearance) {
state.value = state.value.copy(appearance = appearance)
}
override fun reload() = Unit
override fun enroll(passphrase: String) {
submitted = passphrase
@@ -192,6 +192,9 @@ class EntryViewerTest {
}
}
override val state = lease
override fun setAppearance(appearance: de.rfc1437.ironstorage.core.Appearance) {
state.value = state.value.copy(appearance = appearance)
}
override fun reload() = Unit
override fun unlock() = Unit
override fun enroll(passphrase: String) = Unit
@@ -184,6 +184,9 @@ class PasswordScreenTest {
}
}
override val state = lease
override fun setAppearance(appearance: de.rfc1437.ironstorage.core.Appearance) {
state.value = state.value.copy(appearance = appearance)
}
override fun reload() = Unit
override fun unlock() = error("Unexpected direct unlock")
override fun enroll(passphrase: String) = error("Unexpected enrollment")
@@ -13,13 +13,20 @@ import android.view.WindowManager
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.luminance
import androidx.core.view.WindowCompat
import androidx.fragment.app.FragmentActivity
import dagger.hilt.android.AndroidEntryPoint
import de.rfc1437.ironstorage.core.AuthenticationLifecycle
import de.rfc1437.ironstorage.core.AuthenticationRepository
import de.rfc1437.ironstorage.core.OnboardingRepository
import de.rfc1437.ironstorage.core.ShellRepository
import de.rfc1437.ironstorage.features.ApplicationTheme
import de.rfc1437.ironstorage.features.ProtectedContent
import de.rfc1437.ironstorage.features.SetupFormViewModel
import de.rfc1437.ironstorage.features.ShellViewModel
@@ -132,10 +139,19 @@ class MainActivity : FragmentActivity() {
enableEdgeToEdge()
setContent {
ProtectedContent(authentication.presentation) {
MaterialTheme {
ApplicationTheme(authentication) {
val lightBars = MaterialTheme.colorScheme.background.luminance() > 0.5f
SideEffect {
WindowCompat.getInsetsController(window, window.decorView).apply {
isAppearanceLightStatusBars = lightBars
isAppearanceLightNavigationBars = lightBars
}
}
Surface(Modifier.fillMaxSize()) {
VariantContent(viewModel, repository, setupForm)
}
}
}
}
}
}
@@ -59,6 +59,26 @@ class NativeEntryViewerTest {
val key = setup.generate("Viewer fixture", password).selectedKey
setup.setupLocal(requireNotNull(key), password)
}
for (appearance in Appearance.entries) {
withContext(Dispatchers.Main.immediate) { authentication.setAppearance(appearance) }
withTimeout(30000) {
authentication.state.first {
!it.busy &&
it.appearance == appearance
}
}
assertEquals(appearance, authentication.state.value.appearance)
assertFalse(authentication.state.value.unlocked)
}
val restoredAppearance = NativeAuthenticationRepository(session, scope, Dispatchers.IO)
withContext(Dispatchers.Main.immediate) { restoredAppearance.reload() }
withTimeout(30000) {
restoredAppearance.state.first {
!it.busy &&
it.appearance == Appearance.Dark
}
}
assertFalse(restoredAppearance.state.value.unlocked)
authentication.authenticated { auth ->
val draft = auth.beginCreateEntry("", "viewer", password)
val first = draft.page.fields.first()
@@ -2,6 +2,8 @@ package de.rfc1437.ironstorage.core
import kotlinx.coroutines.flow.StateFlow
enum class Appearance { System, Light, Dark }
enum class AuthenticationFailureKind {
PassphraseRequired,
InvalidPassphrase,
@@ -24,11 +26,13 @@ data class AuthenticationState(
val biometricEnabled: Boolean = false,
val remainingSeconds: ULong = 0u,
val busy: Boolean = false,
val failure: AuthenticationFailure? = null
val failure: AuthenticationFailure? = null,
val appearance: Appearance = Appearance.System
)
interface AuthenticationRepository {
val presentation: ProtectedPresentation
val state: StateFlow<AuthenticationState>
fun setAppearance(appearance: Appearance)
fun reload()
fun unlock()
fun enroll(passphrase: String)
@@ -2,6 +2,7 @@ package de.rfc1437.ironstorage.core
import android.os.SystemClock
import de.rfc1437.ironstorage.bridge.NativeBridgeException
import de.rfc1437.ironstorage.bridge.generated.MobileAppearance
import de.rfc1437.ironstorage.bridge.generated.MobileAuthentication
import de.rfc1437.ironstorage.bridge.generated.MobileAuthenticationErrorKind
import de.rfc1437.ironstorage.bridge.generated.MobileAuthenticationFfiException
@@ -48,7 +49,7 @@ class NativeAuthenticationRepository(
try {
val value = withContext(io) { current?.state() }
if (ticket == sequence.get() && value != null) {
publish(value.toFacade())
publish(value.toFacade().copy(appearance = mutable.value.appearance))
}
} catch (_: MobileAuthenticationFfiException) {
if (ticket == sequence.get()) lock()
@@ -83,14 +84,19 @@ class NativeAuthenticationRepository(
}
result
}
val value = withContext(io) { current!!.state() }
if (ticket == sequence.get()) publish(value.toFacade())
val value = withContext(io) {
current!!.state().toFacade().copy(
appearance = current!!.mobileAppearance().toFacade()
)
}
if (ticket == sequence.get()) publish(value)
result
} catch (error: MobileAuthenticationFfiException.Failed) {
if (ticket == sequence.get()) {
val value = withContext(io) { current?.state() }
publish(
(value?.toFacade() ?: AuthenticationState()).copy(
appearance = mutable.value.appearance,
failure = error.toFacade()
)
)
@@ -110,6 +116,15 @@ class NativeAuthenticationRepository(
}
}
}
override fun setAppearance(appearance: Appearance) = run {
it.setMobileAppearance(
when (appearance) {
Appearance.System -> MobileAppearance.SYSTEM
Appearance.Light -> MobileAppearance.LIGHT
Appearance.Dark -> MobileAppearance.DARK
}
)
}
override fun reload() = run { it.state() }
override fun unlock() = run { it.restoreBiometricLease() }
override fun enroll(passphrase: String) = run { it.enrollBiometricUnlock(passphrase) }
@@ -137,6 +152,7 @@ class NativeAuthenticationRepository(
private fun unavailable() {
publish(
AuthenticationState(
appearance = mutable.value.appearance,
failure = AuthenticationFailure(
AuthenticationFailureKind.Configuration,
"Authentication is unavailable",
@@ -243,3 +259,9 @@ private fun MobileAuthenticationFfiException.Failed.toFacade() = AuthenticationF
title,
detail
)
private fun MobileAppearance.toFacade(): Appearance = when (this) {
MobileAppearance.SYSTEM -> Appearance.System
MobileAppearance.LIGHT -> Appearance.Light
MobileAppearance.DARK -> Appearance.Dark
}
@@ -33,12 +33,14 @@ class NativeFixtureShellRepository internal constructor(
override val snapshot: StateFlow<ShellSnapshot> = mutableSnapshot
private val revision = AtomicLong()
private var job: Job? = null
private var selectedByUi = false
init {
refresh()
}
override fun selectTab(tab: ShellTab) {
selectedByUi = true
mutableSnapshot.update { it.copy(selectedTab = tab) }
}
@@ -63,7 +65,13 @@ class NativeFixtureShellRepository internal constructor(
mutableSnapshot.update {
result.copy(
fixture = fixture,
selectedTab = if (it.pages.isEmpty()) result.selectedTab else it.selectedTab
selectedTab = if (!selectedByUi &&
it.pages.isEmpty()
) {
result.selectedTab
} else {
it.selectedTab
}
)
}
}
@@ -145,6 +153,7 @@ private fun MobileShellState.toFacade(): ShellState = when (this) {
MobileShellState.READY -> ShellState.Ready
MobileShellState.LOCKED -> ShellState.Locked
MobileShellState.ERROR -> ShellState.Error
MobileShellState.STALE -> ShellState.Stale
}
private fun ShellState.toNative(): MobileShellState = when (this) {
@@ -153,6 +162,7 @@ private fun ShellState.toNative(): MobileShellState = when (this) {
ShellState.Ready -> MobileShellState.READY
ShellState.Locked -> MobileShellState.LOCKED
ShellState.Error -> MobileShellState.ERROR
ShellState.Stale -> MobileShellState.STALE
}
private fun MobileTab.toFacade(): ShellTab = when (this) {
@@ -80,6 +80,7 @@ class NativeOnboardingRepository(
}
}
val discovery = request.discover()
if (revoked.get()) throw interrupted()
pollProgress = false
mutableSetup.update {
it.copy(
@@ -116,6 +117,7 @@ class NativeOnboardingRepository(
}
override fun replaceToken(account: String, token: String) = runSetup {
newOperation().replaceToken(account, token)
if (revoked.get()) throw interrupted()
pollProgress = false
mutableSetup.update {
it.copy(
@@ -143,6 +145,7 @@ class NativeOnboardingRepository(
}
}
private fun completed(outcome: MobileOnboardingOutcome) {
if (revoked.get()) throw interrupted()
updateStatus(requireNotNull(operation).status())
mutableSetup.update {
it.copy(title = outcome.title, detail = outcome.detail, branches = emptyList())
@@ -152,6 +155,7 @@ class NativeOnboardingRepository(
shell.refresh()
}
private fun updateStatus(status: MobileAndroidSetupStatus) {
if (revoked.get()) throw interrupted()
pollProgress = false
mutableSetup.update {
it.copy(
@@ -134,6 +134,11 @@ class NativePasswordRepository(
verified.present(page)
}
}
override fun leave() {
sequence.incrementAndGet()
mutable.value = mutable.value.cleared().copy(loading = false)
authentication.lifecycle(AuthenticationLifecycle.Background)
}
override fun lock() {
sequence.incrementAndGet()
mutable.value = mutable.value.cleared().copy(loading = false)
@@ -80,6 +80,7 @@ interface PasswordRepository {
fun inspect(id: String, revision: String)
fun back()
fun unlock(passphrase: String)
fun leave() = lock()
fun lock()
fun reveal(id: ULong)
fun hide(id: ULong)
@@ -12,23 +12,25 @@ class ProtectedPresentation {
windows += 1
return AutoCloseable { windows -= 1 }
}
private val metadata = linkedSetOf<() -> Unit>()
private val metadata = linkedMapOf<Any, () -> Unit>()
fun registerMetadata(clear: () -> Unit): AutoCloseable {
synchronized(metadata) { metadata.add(clear) }
return AutoCloseable { synchronized(metadata) { metadata.remove(clear) } }
val registration = Any()
synchronized(metadata) { metadata[registration] = clear }
return AutoCloseable { synchronized(metadata) { metadata.remove(registration) } }
}
fun clearMetadata() {
synchronized(metadata) { metadata.toList() }.forEach { it() }
synchronized(metadata) { metadata.values.toList() }.forEach { it() }
}
private val listeners = linkedSetOf<() -> Unit>()
private val listeners = linkedMapOf<Any, () -> Unit>()
private val mutableGeneration = MutableStateFlow(0L)
val generation: StateFlow<Long> = mutableGeneration
fun register(clear: () -> Unit): AutoCloseable {
synchronized(listeners) { listeners.add(clear) }
return AutoCloseable { synchronized(listeners) { listeners.remove(clear) } }
val registration = Any()
synchronized(listeners) { listeners[registration] = clear }
return AutoCloseable { synchronized(listeners) { listeners.remove(registration) } }
}
fun clear() {
synchronized(listeners) { listeners.toList() }.forEach { it() }
synchronized(listeners) { listeners.values.toList() }.forEach { it() }
mutableGeneration.value += 1
}
}
@@ -2,7 +2,7 @@ package de.rfc1437.ironstorage.core
import kotlinx.coroutines.flow.StateFlow
enum class ShellState { Loading, Empty, Ready, Locked, Error }
enum class ShellState { Loading, Empty, Ready, Locked, Error, Stale }
enum class ShellTab { Home, Passwords, Search, Totp, Preferences }
enum class ShellIcon {
@@ -10,6 +10,19 @@ import org.junit.Test
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class NativeFixtureShellRepositoryTest {
@Test
fun restoredTabWinsOverInitialNativeCompletion() = runTest {
val pending = CompletableDeferred<Unit>()
val repository = NativeFixtureShellRepository(backgroundScope) { state ->
pending.await()
ShellSnapshot(state, emptyList(), selectedTab = ShellTab.Home)
}
repository.selectTab(ShellTab.Preferences)
pending.complete(Unit)
runCurrent()
assertEquals(ShellTab.Preferences, repository.snapshot.value.selectedTab)
}
@Test
fun cancelledNativeCompletionCannotReplaceNewerStateOrTabSelection() = runTest {
val pending = CompletableDeferred<Unit>()
@@ -29,4 +29,31 @@ class ProtectedPresentationTest {
assertEquals("disposed", secret)
assertEquals(2L, presentation.generation.value)
}
@Test fun overlappingScreensCannotUnregisterAnotherOwnersIdenticalCallback() {
val presentation = ProtectedPresentation()
for (metadata in listOf(false, true)) {
var calls = 0
val callback = { calls += 1 }
fun register() = if (metadata) {
presentation.registerMetadata(
callback
)
} else {
presentation.register(callback)
}
fun clear() = if (metadata) presentation.clearMetadata() else presentation.clear()
val first = register()
val second = register()
clear()
assertEquals(2, calls)
first.close()
first.close()
clear()
assertEquals(3, calls)
second.close()
clear()
assertEquals(3, calls)
}
}
}
@@ -0,0 +1,25 @@
package de.rfc1437.ironstorage.features
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.rfc1437.ironstorage.core.Appearance
import de.rfc1437.ironstorage.core.AuthenticationRepository
@Composable
fun ApplicationTheme(authentication: AuthenticationRepository, content: @Composable () -> Unit) {
val state by authentication.state.collectAsStateWithLifecycle()
val dark = when (state.appearance) {
Appearance.System -> isSystemInDarkTheme()
Appearance.Light -> false
Appearance.Dark -> true
}
MaterialTheme(
colorScheme = if (dark) darkColorScheme() else lightColorScheme(),
content = content
)
}
@@ -46,13 +46,17 @@ import de.rfc1437.ironstorage.core.ShellIcon
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(repository: HomeRepository, modifier: Modifier = Modifier) {
fun HomeScreen(
repository: HomeRepository,
modifier: Modifier = Modifier,
leaveOnDispose: Boolean = true
) {
val state by repository.state.collectAsStateWithLifecycle()
var committing by remember { mutableStateOf(false) }
var message by remember { mutableStateOf("") }
DisposableEffect(repository) {
repository.enter()
onDispose { repository.leave() }
onDispose { if (leaveOnDispose) repository.leave() }
}
LifecycleEventEffect(Lifecycle.Event.ON_STOP) {
committing = false
@@ -54,7 +54,8 @@ class SetupFormViewModel @Inject constructor() : ViewModel() {
fun OnboardingScreen(
repository: OnboardingRepository,
secrets: SetupFormViewModel,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
scroll: Boolean = true
) {
ProtectedInputSurface()
val state by repository.setup.collectAsStateWithLifecycle()
@@ -77,7 +78,9 @@ fun OnboardingScreen(
else -> step
}
Column(
modifier.widthIn(max = 640.dp).fillMaxWidth().verticalScroll(rememberScrollState()),
modifier.widthIn(max = 640.dp).fillMaxWidth().then(
if (scroll) Modifier.verticalScroll(rememberScrollState()) else Modifier
),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
@@ -56,11 +56,12 @@ import de.rfc1437.ironstorage.core.ShellIcon
fun PasswordScreen(
repository: PasswordRepository,
authentication: AuthenticationRepository,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
leaveOnDispose: Boolean = true
) {
DisposableEffect(repository, authentication) {
onDispose {
if (authentication.state.value.unlocked) repository.lock()
if (leaveOnDispose) repository.leave()
}
}
val state by repository.state.collectAsStateWithLifecycle()
@@ -87,7 +88,7 @@ fun PasswordScreen(
LifecycleEventEffect(Lifecycle.Event.ON_STOP) {
manual = false
requested = false
repository.lock()
repository.leave()
}
LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
if (!state.loading && state.revision.isNotEmpty()) repository.refresh()
@@ -39,14 +39,15 @@ import de.rfc1437.ironstorage.core.SearchRepository
fun SearchScreen(
repository: SearchRepository,
open: (PasswordRow) -> Unit,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
leaveOnDispose: Boolean = true
) {
val state by repository.state.collectAsStateWithLifecycle()
val keyboard = LocalSoftwareKeyboardController.current
val focus = LocalFocusManager.current
DisposableEffect(repository) {
repository.refresh()
onDispose { repository.cancel() }
onDispose { if (leaveOnDispose) repository.cancel() }
}
LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { repository.refresh() }
LifecycleEventEffect(Lifecycle.Event.ON_STOP) { repository.cancel() }
@@ -1,83 +1,233 @@
package de.rfc1437.ironstorage.features
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.NavigationRailItem
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.LiveRegionMode
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.liveRegion
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation3.runtime.NavEntry
import androidx.navigation3.ui.NavDisplay
import de.rfc1437.ironstorage.core.Appearance
import de.rfc1437.ironstorage.core.NativeFailure
import de.rfc1437.ironstorage.core.ShellIcon
import de.rfc1437.ironstorage.core.ShellSnapshot
import de.rfc1437.ironstorage.core.ShellState
import de.rfc1437.ironstorage.core.ShellTab
private enum class ShellRoute(val tab: ShellTab?) {
Home(ShellTab.Home),
Passwords(ShellTab.Passwords),
Search(ShellTab.Search),
Totp(ShellTab.Totp),
Preferences(ShellTab.Preferences),
Appearance(null)
}
@Composable
fun ShellScreen(viewModel: ShellViewModel, setupForm: SetupFormViewModel) {
val snapshot by viewModel.snapshot.collectAsStateWithLifecycle()
val appearancePage by viewModel.appearancePage.collectAsStateWithLifecycle()
fun selectTab(tab: ShellTab) {
if (tab != snapshot.selectedTab) setupForm.clearSecrets()
viewModel.selectTab(tab)
}
val route = when (snapshot.selectedTab) {
ShellTab.Home -> ShellRoute.Home
ShellTab.Passwords -> ShellRoute.Passwords
ShellTab.Search -> ShellRoute.Search
ShellTab.Totp -> ShellRoute.Totp
ShellTab.Preferences -> ShellRoute.Preferences
}
val backStack = if (route == ShellRoute.Preferences && appearancePage && !snapshot.fixture) {
listOf(route, ShellRoute.Appearance)
} else {
listOf(route)
}
// Actual window bounds, rather than device type, also handle desktop and split-screen resize.
BoxWithConstraints(Modifier.fillMaxSize()) {
val rail = maxWidth >= 600.dp
val largeFont = LocalDensity.current.fontScale >= 1.6f
Scaffold(
bottomBar = {
NavigationBar {
if (!rail) {
NavigationBar(Modifier.testTag("shell-bottom-navigation")) {
snapshot.pages.forEach { page ->
val tab = page.tab
NavigationBarItem(
selected = snapshot.selectedTab == tab,
onClick = { viewModel.selectTab(tab) },
modifier = Modifier.testTag("shell-tab-${page.tab.name}"),
selected = snapshot.selectedTab == page.tab,
onClick = { selectTab(page.tab) },
icon = {
Icon(
painterResource(navigationIcon(page.icon)),
contentDescription = null
if (largeFont) page.title else null
)
},
label = if (largeFont) null else ({ Text(page.title) })
)
}
}
}
}
) { padding ->
Row(Modifier.fillMaxSize().padding(padding)) {
if (rail) {
NavigationRail(
Modifier.testTag(
"shell-navigation-rail"
).verticalScroll(rememberScrollState())
) {
snapshot.pages.forEach { page ->
NavigationRailItem(
modifier = Modifier.testTag("shell-tab-${page.tab.name}"),
selected = snapshot.selectedTab == page.tab,
onClick = { selectTab(page.tab) },
icon = { Icon(painterResource(navigationIcon(page.icon)), null) },
label = { Text(page.title) }
)
}
}
}
) { padding ->
NavDisplay(
backStack = backStack,
modifier = Modifier.weight(
1f
).fillMaxSize().padding(16.dp).testTag("shell-content"),
// Screen inputs are never saved by Navigation 3.
entryDecorators = emptyList(),
onBack = viewModel::backAppearance,
entryProvider = { key ->
NavEntry(key) {
if (key == ShellRoute.Appearance) {
AppearanceScreen(viewModel)
} else {
ShellDestination(key, snapshot, viewModel, setupForm)
}
}
}
)
}
}
}
}
@Composable
private fun ShellDestination(
route: ShellRoute,
snapshot: ShellSnapshot,
viewModel: ShellViewModel,
setupForm: SetupFormViewModel
) {
val ready =
!snapshot.fixture && snapshot.nativeFailure == null && snapshot.storageRecovery == null &&
(snapshot.state == ShellState.Ready || snapshot.state == ShellState.Locked)
when {
!snapshot.fixture && snapshot.nativeFailure == null && snapshot.storageRecovery == null &&
(snapshot.state == ShellState.Empty || route == ShellRoute.Preferences) -> {
Column(
modifier = Modifier.fillMaxSize().padding(padding).padding(24.dp),
Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
if (ready && route == ShellRoute.Preferences) {
TextButton(
onClick = {
setupForm.clearSecrets()
viewModel.openAppearance()
},
modifier = Modifier.testTag("shell-open-appearance")
) {
Text(stringResource(R.string.shell_appearance))
}
AuthenticationControls(viewModel.authentication)
}
OnboardingScreen(viewModel.onboarding, setupForm, scroll = false)
}
}
ready && route == ShellRoute.Passwords -> {
LaunchedEffect(viewModel) { viewModel.openPasswords() }
PasswordScreen(
viewModel.passwords,
viewModel.authentication,
Modifier.fillMaxSize(),
leaveOnDispose = false
)
}
ready && route == ShellRoute.Search -> SearchScreen(
viewModel.search,
viewModel::openSearch,
Modifier.fillMaxSize(),
leaveOnDispose = false
)
ready && route == ShellRoute.Home -> HomeScreen(
viewModel.home,
Modifier.fillMaxSize(),
leaveOnDispose = false
)
else -> {
val page = snapshot.pages.firstOrNull { it.tab == route.tab }
Column(
Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
val ready = !snapshot.fixture && snapshot.nativeFailure == null &&
snapshot.storageRecovery == null &&
(snapshot.state == ShellState.Ready || snapshot.state == ShellState.Locked)
val browsing = ready &&
snapshot.selectedTab == ShellTab.Passwords
val searching = ready && snapshot.selectedTab == ShellTab.Search
val home = ready && snapshot.selectedTab == ShellTab.Home
if (!browsing && !home && !searching) {
Text(
stringResource(R.string.app_name),
style = MaterialTheme.typography.headlineLarge
)
}
val page = snapshot.pages.firstOrNull { it.tab == snapshot.selectedTab }
val nativeFailure = snapshot.nativeFailure
val title = if (nativeFailure != null) {
val failure = snapshot.nativeFailure
Text(
if (failure !=
null
) {
stringResource(R.string.native_failure_title)
} else {
page?.stateTitle ?: stringResource(R.string.scaffold_title)
}
val detail = if (nativeFailure == null) {
page?.stateTitle
?: stringResource(R.string.scaffold_title)
},
modifier = Modifier.semantics {
heading()
liveRegion = LiveRegionMode.Polite
},
style = MaterialTheme.typography.headlineSmall
)
Text(
if (failure ==
null
) {
page?.detail ?: stringResource(R.string.scaffold_detail)
} else {
stringResource(
when (nativeFailure) {
when (failure) {
NativeFailure.UnsupportedAbi -> R.string.native_unsupported_device
NativeFailure.MissingLibrary,
NativeFailure.ContractMismatch,
@@ -88,32 +238,64 @@ fun ShellScreen(viewModel: ShellViewModel, setupForm: SetupFormViewModel) {
}
)
}
if (!snapshot.fixture && snapshot.nativeFailure == null &&
snapshot.storageRecovery == null &&
(snapshot.state == ShellState.Empty || snapshot.selectedTab == ShellTab.Preferences)
) {
if (ready && snapshot.selectedTab == ShellTab.Preferences) {
AuthenticationControls(viewModel.authentication)
}
OnboardingScreen(viewModel.onboarding, setupForm)
} else if (browsing) {
LaunchedEffect(viewModel) { viewModel.openPasswords() }
PasswordScreen(viewModel.passwords, viewModel.authentication, Modifier.weight(1f))
} else if (searching) {
SearchScreen(viewModel.search, viewModel::openSearch, Modifier.weight(1f))
} else if (home) {
HomeScreen(viewModel.home, Modifier.weight(1f))
} else {
Text(
title,
modifier = Modifier.semantics { heading() },
style = MaterialTheme.typography.headlineSmall
)
Text(detail)
if (snapshot.state == ShellState.Loading) CircularProgressIndicator()
if (!snapshot.fixture) {
TextButton(onClick = viewModel::refreshShell) {
Text(stringResource(R.string.shell_refresh))
}
}
}
}
}
}
@Composable
private fun AppearanceScreen(viewModel: ShellViewModel) {
val state by viewModel.authentication.state.collectAsStateWithLifecycle()
Column(
Modifier.fillMaxSize().verticalScroll(rememberScrollState()).selectableGroup(),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
TextButton(onClick = viewModel::backAppearance) {
Text(stringResource(R.string.password_back))
}
Text(
stringResource(R.string.shell_appearance),
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.semantics {
heading()
}
)
Appearance.entries.forEach { appearance ->
val label = stringResource(
when (appearance) {
Appearance.System -> R.string.shell_appearance_system
Appearance.Light -> R.string.shell_appearance_light
Appearance.Dark -> R.string.shell_appearance_dark
}
)
Row(
Modifier.fillMaxWidth().selectable(
selected = state.appearance == appearance,
enabled = !state.busy,
role = Role.RadioButton,
onClick = { viewModel.authentication.setAppearance(appearance) }
).padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(label)
RadioButton(
selected = state.appearance == appearance,
enabled = !state.busy,
onClick = null
)
}
}
state.failure?.let {
Text(it.title + "\n" + it.detail, color = MaterialTheme.colorScheme.error)
}
}
}
private fun navigationIcon(icon: ShellIcon): Int = when (icon) {
@@ -11,8 +11,11 @@ import de.rfc1437.ironstorage.core.PasswordRepository
import de.rfc1437.ironstorage.core.PasswordRow
import de.rfc1437.ironstorage.core.SearchRepository
import de.rfc1437.ironstorage.core.ShellRepository
import de.rfc1437.ironstorage.core.ShellState
import de.rfc1437.ironstorage.core.ShellTab
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
@HiltViewModel
@@ -26,8 +29,29 @@ class ShellViewModel @Inject constructor(
private val saved: SavedStateHandle
) : ViewModel() {
val snapshot = repository.snapshot
private val mutableAppearancePage =
MutableStateFlow(saved.get<Boolean>("shell.appearance") ?: false)
val appearancePage = mutableAppearancePage.asStateFlow()
fun openAppearance() {
onboarding.cancel()
if (authentication.state.value.busy) authentication.cancel()
saved["shell.appearance"] = true
mutableAppearancePage.value = true
}
fun backAppearance() {
saved["shell.appearance"] = false
mutableAppearancePage.value = false
}
init {
val restored = saved.get<String>("shell.tab")?.let { name ->
ShellTab.entries.firstOrNull { it.name == name }
}
if (restored != null) repository.selectTab(restored)
viewModelScope.launch {
snapshot.collect { saved["shell.tab"] = it.selectedTab.name }
}
viewModelScope.launch {
passwords.state.collect { value ->
if (!value.loading && value.error == null && value.revision.isNotEmpty()) {
@@ -41,7 +65,7 @@ class ShellViewModel @Inject constructor(
private var passwordsRestored = false
fun openSearch(row: PasswordRow) {
passwords.openSearch(row.id, row.revision)
repository.selectTab(ShellTab.Passwords)
selectTab(ShellTab.Passwords)
}
fun openPasswords() {
if (!passwordsRestored) {
@@ -58,10 +82,28 @@ class ShellViewModel @Inject constructor(
passwords.refresh()
}
}
fun refreshShell() = repository.refresh()
fun selectTab(tab: ShellTab) {
if (tab != snapshot.value.selectedTab) leaveDestination()
saved["shell.tab"] = tab.name
repository.selectTab(tab)
}
private fun leaveDestination() {
val current = snapshot.value
if (current.fixture) return
if (current.state == ShellState.Empty || current.selectedTab == ShellTab.Preferences) {
onboarding.cancel()
}
when (current.selectedTab) {
ShellTab.Home -> home.leave()
ShellTab.Passwords -> passwords.leave()
ShellTab.Search -> search.cancel()
ShellTab.Preferences -> if (authentication.state.value.busy) authentication.cancel()
ShellTab.Totp -> Unit
}
}
override fun onCleared() {
leaveDestination()
passwords.lock()
}
}
@@ -113,4 +113,9 @@
<string name="search_count">Results: %1$d</string>
<string name="search_open">Open entry</string>
<string name="search_cancelled">Search cancelled. Refresh to try again.</string>
<string name="shell_appearance">Appearance</string>
<string name="shell_appearance_system">System default</string>
<string name="shell_appearance_light">Light</string>
<string name="shell_appearance_dark">Dark</string>
<string name="shell_refresh">Reload state</string>
</resources>
File diff suppressed because it is too large. Load diff
+361
View File
@@ -469,6 +469,16 @@ void uniffi_ironstorage_mobile_fn_method_mobileauthentication_cancel(uint64_t pt
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_copy_entry_field(uint64_t ptr, RustBuffer path, uint64_t field, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_COPY_PRESENTED_ENTRY_FIELD
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_COPY_PRESENTED_ENTRY_FIELD
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_copy_presented_entry_field(uint64_t ptr, RustBuffer path, uint64_t field_id, RustBuffer revision, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_COPY_PRESENTED_TOTP_CODE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_COPY_PRESENTED_TOTP_CODE
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_copy_presented_totp_code(uint64_t ptr, RustBuffer path, RustBuffer revision, uint64_t unix_seconds, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_COPY_TOTP_CODE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_COPY_TOTP_CODE
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_copy_totp_code(uint64_t ptr, RustBuffer path, uint64_t unix_seconds, RustCallStatus *_Nonnull out_status
@@ -484,6 +494,11 @@ void uniffi_ironstorage_mobile_fn_method_mobileauthentication_create_directory(u
void uniffi_ironstorage_mobile_fn_method_mobileauthentication_discard_entry_editor(uint64_t ptr, uint64_t editor, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_ENROLL_BIOMETRIC_UNLOCK
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_ENROLL_BIOMETRIC_UNLOCK
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_enroll_biometric_unlock(uint64_t ptr, RustBuffer passphrase, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_ENTRY_EDITOR
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_ENTRY_EDITOR
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_entry_editor(uint64_t ptr, uint64_t editor, RustCallStatus *_Nonnull out_status
@@ -519,6 +534,11 @@ void uniffi_ironstorage_mobile_fn_method_mobileauthentication_manual_lock(uint64
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_mobile_appearance(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_NAVIGATION_ENTRY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_NAVIGATION_ENTRY
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_navigation_entry(uint64_t ptr, RustBuffer directory, RustBuffer row_id, RustBuffer revision, RustBuffer passphrase, uint64_t unix_seconds, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_PERFORM_ENTRY_MUTATION
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_PERFORM_ENTRY_MUTATION
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_perform_entry_mutation(uint64_t ptr, RustBuffer request, RustCallStatus *_Nonnull out_status
@@ -534,11 +554,21 @@ RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_preferences(
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_prepare_entry_mutation(uint64_t ptr, RustBuffer path, RustBuffer action, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_PREPARE_PRESENTED_ENTRY_MUTATION
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_PREPARE_PRESENTED_ENTRY_MUTATION
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_prepare_presented_entry_mutation(uint64_t ptr, RustBuffer path, RustBuffer revision, RustBuffer action, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_PREPARE_WATCH_SNAPSHOT
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_PREPARE_WATCH_SNAPSHOT
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_prepare_watch_snapshot(uint64_t ptr, RustBuffer platform_pairing_identity, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_REFRESH_ENTRY_PRESENTATION
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_REFRESH_ENTRY_PRESENTATION
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_refresh_entry_presentation(uint64_t ptr, RustBuffer path, RustBuffer revision, uint64_t unix_seconds, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_REMOVE_APPLICATION_TOKEN
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_REMOVE_APPLICATION_TOKEN
void uniffi_ironstorage_mobile_fn_method_mobileauthentication_remove_application_token(uint64_t ptr, RustCallStatus *_Nonnull out_status
@@ -559,11 +589,26 @@ RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_reorder_entr
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_replace_entry_field(uint64_t ptr, RustBuffer path, uint64_t field, RustBuffer value, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_REPLACE_PRESENTED_ENTRY_FIELD
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_REPLACE_PRESENTED_ENTRY_FIELD
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_replace_presented_entry_field(uint64_t ptr, RustBuffer path, uint64_t field_id, RustBuffer revision, RustBuffer value, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_RESTORE_BIOMETRIC_LEASE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_RESTORE_BIOMETRIC_LEASE
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_restore_biometric_lease(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_REVEAL_ENTRY_FIELD
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_REVEAL_ENTRY_FIELD
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_reveal_entry_field(uint64_t ptr, RustBuffer path, uint64_t field, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_REVEAL_PRESENTED_ENTRY_FIELD
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_REVEAL_PRESENTED_ENTRY_FIELD
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_reveal_presented_entry_field(uint64_t ptr, RustBuffer path, uint64_t field_id, RustBuffer revision, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_SAVE_ENTRY_EDITOR
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEAUTHENTICATION_SAVE_ENTRY_EDITOR
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileauthentication_save_entry_editor(uint64_t ptr, uint64_t editor, RustBuffer fields, RustCallStatus *_Nonnull out_status
@@ -659,6 +704,11 @@ uint64_t uniffi_ironstorage_mobile_fn_clone_mobilehomeoperation(uint64_t handle,
void uniffi_ironstorage_mobile_fn_free_mobilehomeoperation(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEHOMEOPERATION_ACTIVITY_KNOWN
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEHOMEOPERATION_ACTIVITY_KNOWN
int8_t uniffi_ironstorage_mobile_fn_method_mobilehomeoperation_activity_known(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEHOMEOPERATION_CACHED
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEHOMEOPERATION_CACHED
RustBuffer uniffi_ironstorage_mobile_fn_method_mobilehomeoperation_cached(uint64_t ptr, RustCallStatus *_Nonnull out_status
@@ -679,6 +729,11 @@ RustBuffer uniffi_ironstorage_mobile_fn_method_mobilehomeoperation_commit(uint64
RustBuffer uniffi_ironstorage_mobile_fn_method_mobilehomeoperation_fetch(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEHOMEOPERATION_IDENTITY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEHOMEOPERATION_IDENTITY
RustBuffer uniffi_ironstorage_mobile_fn_method_mobilehomeoperation_identity(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEHOMEOPERATION_PROGRESS
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEHOMEOPERATION_PROGRESS
RustBuffer uniffi_ironstorage_mobile_fn_method_mobilehomeoperation_progress(uint64_t ptr, RustCallStatus *_Nonnull out_status
@@ -694,6 +749,11 @@ RustBuffer uniffi_ironstorage_mobile_fn_method_mobilehomeoperation_pull(uint64_t
RustBuffer uniffi_ironstorage_mobile_fn_method_mobilehomeoperation_push(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEHOMEOPERATION_REFRESH_GESTURE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEHOMEOPERATION_REFRESH_GESTURE
RustBuffer uniffi_ironstorage_mobile_fn_method_mobilehomeoperation_refresh_gesture(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEHOMEOPERATION_REFRESH_IF_STALE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEHOMEOPERATION_REFRESH_IF_STALE
RustBuffer uniffi_ironstorage_mobile_fn_method_mobilehomeoperation_refresh_if_stale(uint64_t ptr, RustCallStatus *_Nonnull out_status
@@ -874,6 +934,86 @@ RustBuffer uniffi_ironstorage_mobile_fn_method_mobileandroidkeystore_aliases(uin
void uniffi_ironstorage_mobile_fn_method_mobileandroidkeystore_cancel_pending(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_CLONE_MOBILEANDROIDONBOARDING
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_CLONE_MOBILEANDROIDONBOARDING
uint64_t uniffi_ironstorage_mobile_fn_clone_mobileandroidonboarding(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_FREE_MOBILEANDROIDONBOARDING
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_FREE_MOBILEANDROIDONBOARDING
void uniffi_ironstorage_mobile_fn_free_mobileandroidonboarding(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDONBOARDING_CANCEL
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDONBOARDING_CANCEL
void uniffi_ironstorage_mobile_fn_method_mobileandroidonboarding_cancel(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDONBOARDING_GENERATE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDONBOARDING_GENERATE
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileandroidonboarding_generate(uint64_t ptr, RustBuffer user_id, RustBuffer passphrase, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDONBOARDING_IMPORT_KEY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDONBOARDING_IMPORT_KEY
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileandroidonboarding_import_key(uint64_t ptr, RustBuffer armor, RustBuffer passphrase, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDONBOARDING_PROGRESS
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDONBOARDING_PROGRESS
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileandroidonboarding_progress(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDONBOARDING_REMOTE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDONBOARDING_REMOTE
uint64_t uniffi_ironstorage_mobile_fn_method_mobileandroidonboarding_remote(uint64_t ptr, RustBuffer server_url, RustBuffer account, RustBuffer repository_path, RustBuffer application_token, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDONBOARDING_REPLACE_TOKEN
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDONBOARDING_REPLACE_TOKEN
void uniffi_ironstorage_mobile_fn_method_mobileandroidonboarding_replace_token(uint64_t ptr, RustBuffer account, RustBuffer application_token, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDONBOARDING_SETUP_LOCAL
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDONBOARDING_SETUP_LOCAL
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileandroidonboarding_setup_local(uint64_t ptr, RustBuffer fingerprint, RustBuffer passphrase, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDONBOARDING_STATUS
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDONBOARDING_STATUS
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileandroidonboarding_status(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_CLONE_MOBILEANDROIDREMOTEONBOARDING
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_CLONE_MOBILEANDROIDREMOTEONBOARDING
uint64_t uniffi_ironstorage_mobile_fn_clone_mobileandroidremoteonboarding(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_FREE_MOBILEANDROIDREMOTEONBOARDING
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_FREE_MOBILEANDROIDREMOTEONBOARDING
void uniffi_ironstorage_mobile_fn_free_mobileandroidremoteonboarding(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDREMOTEONBOARDING_CANCEL
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDREMOTEONBOARDING_CANCEL
void uniffi_ironstorage_mobile_fn_method_mobileandroidremoteonboarding_cancel(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDREMOTEONBOARDING_CLONE_STORE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDREMOTEONBOARDING_CLONE_STORE
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileandroidremoteonboarding_clone_store(uint64_t ptr, RustBuffer branch, RustBuffer fingerprint, RustBuffer passphrase, int8_t use_existing, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDREMOTEONBOARDING_CONNECT
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDREMOTEONBOARDING_CONNECT
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileandroidremoteonboarding_connect(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDREMOTEONBOARDING_DISCOVER
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDREMOTEONBOARDING_DISCOVER
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileandroidremoteonboarding_discover(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_CLONE_MOBILECLIPBOARD
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_CLONE_MOBILECLIPBOARD
uint64_t uniffi_ironstorage_mobile_fn_clone_mobileclipboard(uint64_t handle, RustCallStatus *_Nonnull out_status
@@ -984,11 +1124,36 @@ uint64_t uniffi_ironstorage_mobile_fn_clone_mobileprivateruntime(uint64_t handle
void uniffi_ironstorage_mobile_fn_free_mobileprivateruntime(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_OPEN_PASSWORD_SEARCH_RESULT
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_OPEN_PASSWORD_SEARCH_RESULT
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileprivateruntime_open_password_search_result(uint64_t ptr, RustBuffer id, RustBuffer revision, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_PASSWORD_NAME_SEARCH
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_PASSWORD_NAME_SEARCH
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileprivateruntime_password_name_search(uint64_t ptr, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_PASSWORD_NAVIGATION
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_PASSWORD_NAVIGATION
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileprivateruntime_password_navigation(uint64_t ptr, RustBuffer path, RustBuffer selected, RustBuffer revision, int8_t activate, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_ACTIVATE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_ACTIVATE
void uniffi_ironstorage_mobile_fn_method_mobileprivateruntime_activate(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_ANDROID_HOME_OPERATION
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_ANDROID_HOME_OPERATION
uint64_t uniffi_ironstorage_mobile_fn_method_mobileprivateruntime_android_home_operation(uint64_t ptr, uint64_t backend, uint64_t clock, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_ANDROID_ONBOARDING
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_ANDROID_ONBOARDING
uint64_t uniffi_ironstorage_mobile_fn_method_mobileprivateruntime_android_onboarding(uint64_t ptr, uint64_t backend, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_ANDROID_SECRET_BACKEND
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_ANDROID_SECRET_BACKEND
uint64_t uniffi_ironstorage_mobile_fn_method_mobileprivateruntime_android_secret_backend(uint64_t ptr, uint64_t keystore, RustCallStatus *_Nonnull out_status
@@ -1101,11 +1266,21 @@ void uniffi_ironstorage_mobile_fn_func_replace_configured_mobile_application_tok
void uniffi_ironstorage_mobile_fn_func_set_selected_mobile_tab(RustBuffer tab, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_FUNC_CLEAR_MOBILE_ENTRY_COPY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_FUNC_CLEAR_MOBILE_ENTRY_COPY
void uniffi_ironstorage_mobile_fn_func_clear_mobile_entry_copy(RustBuffer copy, uint64_t clipboard, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_FUNC_COPY_MOBILE_ENTRY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_FUNC_COPY_MOBILE_ENTRY
RustBuffer uniffi_ironstorage_mobile_fn_func_copy_mobile_entry(RustBuffer copy, uint64_t clipboard, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_FUNC_COPY_MOBILE_ENTRY_CLEARING
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_FUNC_COPY_MOBILE_ENTRY_CLEARING
RustBuffer uniffi_ironstorage_mobile_fn_func_copy_mobile_entry_clearing(RustBuffer copy, uint64_t clipboard, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_FUNC_DECODE_MOBILE_CAMERA_FRAME
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_FUNC_DECODE_MOBILE_CAMERA_FRAME
RustBuffer uniffi_ironstorage_mobile_fn_func_decode_mobile_camera_frame(RustBuffer frame, RustCallStatus *_Nonnull out_status
@@ -1478,12 +1653,24 @@ uint16_t uniffi_ironstorage_mobile_checksum_func_replace_configured_mobile_appli
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_FUNC_SET_SELECTED_MOBILE_TAB
uint16_t uniffi_ironstorage_mobile_checksum_func_set_selected_mobile_tab(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_FUNC_CLEAR_MOBILE_ENTRY_COPY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_FUNC_CLEAR_MOBILE_ENTRY_COPY
uint16_t uniffi_ironstorage_mobile_checksum_func_clear_mobile_entry_copy(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_FUNC_COPY_MOBILE_ENTRY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_FUNC_COPY_MOBILE_ENTRY
uint16_t uniffi_ironstorage_mobile_checksum_func_copy_mobile_entry(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_FUNC_COPY_MOBILE_ENTRY_CLEARING
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_FUNC_COPY_MOBILE_ENTRY_CLEARING
uint16_t uniffi_ironstorage_mobile_checksum_func_copy_mobile_entry_clearing(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_FUNC_DECODE_MOBILE_CAMERA_FRAME
@@ -1568,6 +1755,18 @@ uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_cancel(v
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_COPY_ENTRY_FIELD
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_copy_entry_field(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_COPY_PRESENTED_ENTRY_FIELD
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_COPY_PRESENTED_ENTRY_FIELD
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_copy_presented_entry_field(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_COPY_PRESENTED_TOTP_CODE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_COPY_PRESENTED_TOTP_CODE
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_copy_presented_totp_code(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_COPY_TOTP_CODE
@@ -1586,6 +1785,12 @@ uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_create_d
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_DISCARD_ENTRY_EDITOR
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_discard_entry_editor(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_ENROLL_BIOMETRIC_UNLOCK
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_ENROLL_BIOMETRIC_UNLOCK
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_enroll_biometric_unlock(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_ENTRY_EDITOR
@@ -1628,6 +1833,12 @@ uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_manual_l
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_MOBILE_APPEARANCE
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_mobile_appearance(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_NAVIGATION_ENTRY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_NAVIGATION_ENTRY
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_navigation_entry(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_PERFORM_ENTRY_MUTATION
@@ -1646,12 +1857,24 @@ uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_preferen
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_PREPARE_ENTRY_MUTATION
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_prepare_entry_mutation(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_PREPARE_PRESENTED_ENTRY_MUTATION
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_PREPARE_PRESENTED_ENTRY_MUTATION
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_prepare_presented_entry_mutation(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_PREPARE_WATCH_SNAPSHOT
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_PREPARE_WATCH_SNAPSHOT
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_prepare_watch_snapshot(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REFRESH_ENTRY_PRESENTATION
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REFRESH_ENTRY_PRESENTATION
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_refresh_entry_presentation(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REMOVE_APPLICATION_TOKEN
@@ -1676,12 +1899,30 @@ uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_reorder_
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REPLACE_ENTRY_FIELD
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_replace_entry_field(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REPLACE_PRESENTED_ENTRY_FIELD
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REPLACE_PRESENTED_ENTRY_FIELD
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_replace_presented_entry_field(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_RESTORE_BIOMETRIC_LEASE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_RESTORE_BIOMETRIC_LEASE
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_restore_biometric_lease(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REVEAL_ENTRY_FIELD
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REVEAL_ENTRY_FIELD
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_reveal_entry_field(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REVEAL_PRESENTED_ENTRY_FIELD
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REVEAL_PRESENTED_ENTRY_FIELD
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_reveal_presented_entry_field(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SAVE_ENTRY_EDITOR
@@ -1784,6 +2025,12 @@ uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_watch_sn
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_LIFECYCLE
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileauthentication_lifecycle(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_ACTIVITY_KNOWN
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_ACTIVITY_KNOWN
uint16_t uniffi_ironstorage_mobile_checksum_method_mobilehomeoperation_activity_known(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_CACHED
@@ -1808,6 +2055,12 @@ uint16_t uniffi_ironstorage_mobile_checksum_method_mobilehomeoperation_commit(vo
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_FETCH
uint16_t uniffi_ironstorage_mobile_checksum_method_mobilehomeoperation_fetch(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_IDENTITY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_IDENTITY
uint16_t uniffi_ironstorage_mobile_checksum_method_mobilehomeoperation_identity(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_PROGRESS
@@ -1826,6 +2079,12 @@ uint16_t uniffi_ironstorage_mobile_checksum_method_mobilehomeoperation_pull(void
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_PUSH
uint16_t uniffi_ironstorage_mobile_checksum_method_mobilehomeoperation_push(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_REFRESH_GESTURE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_REFRESH_GESTURE
uint16_t uniffi_ironstorage_mobile_checksum_method_mobilehomeoperation_refresh_gesture(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEHOMEOPERATION_REFRESH_IF_STALE
@@ -1964,6 +2223,78 @@ uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidkeystore_aliases
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDKEYSTORE_CANCEL_PENDING
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidkeystore_cancel_pending(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDONBOARDING_CANCEL
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDONBOARDING_CANCEL
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidonboarding_cancel(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDONBOARDING_GENERATE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDONBOARDING_GENERATE
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidonboarding_generate(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDONBOARDING_IMPORT_KEY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDONBOARDING_IMPORT_KEY
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidonboarding_import_key(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDONBOARDING_PROGRESS
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDONBOARDING_PROGRESS
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidonboarding_progress(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDONBOARDING_REMOTE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDONBOARDING_REMOTE
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidonboarding_remote(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDONBOARDING_REPLACE_TOKEN
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDONBOARDING_REPLACE_TOKEN
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidonboarding_replace_token(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDONBOARDING_SETUP_LOCAL
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDONBOARDING_SETUP_LOCAL
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidonboarding_setup_local(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDONBOARDING_STATUS
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDONBOARDING_STATUS
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidonboarding_status(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDREMOTEONBOARDING_CANCEL
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDREMOTEONBOARDING_CANCEL
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidremoteonboarding_cancel(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDREMOTEONBOARDING_CLONE_STORE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDREMOTEONBOARDING_CLONE_STORE
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidremoteonboarding_clone_store(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDREMOTEONBOARDING_CONNECT
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDREMOTEONBOARDING_CONNECT
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidremoteonboarding_connect(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDREMOTEONBOARDING_DISCOVER
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDREMOTEONBOARDING_DISCOVER
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidremoteonboarding_discover(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILECLIPBOARD_READ
@@ -2030,12 +2361,42 @@ uint16_t uniffi_ironstorage_mobile_checksum_method_mobilesecretbackend_lock(void
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILESECRETBACKEND_UNLOCK
uint16_t uniffi_ironstorage_mobile_checksum_method_mobilesecretbackend_unlock(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_OPEN_PASSWORD_SEARCH_RESULT
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_OPEN_PASSWORD_SEARCH_RESULT
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileprivateruntime_open_password_search_result(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_PASSWORD_NAME_SEARCH
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_PASSWORD_NAME_SEARCH
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileprivateruntime_password_name_search(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_PASSWORD_NAVIGATION
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_PASSWORD_NAVIGATION
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileprivateruntime_password_navigation(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_ACTIVATE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_ACTIVATE
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileprivateruntime_activate(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_ANDROID_HOME_OPERATION
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_ANDROID_HOME_OPERATION
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileprivateruntime_android_home_operation(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_ANDROID_ONBOARDING
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_ANDROID_ONBOARDING
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileprivateruntime_android_onboarding(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_ANDROID_SECRET_BACKEND
+1
View File
@@ -1024,6 +1024,7 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
case .ready: page.systemImage
case .locked: "lock.fill"
case .error: "exclamationmark.triangle"
case .stale: "arrow.clockwise"
}
}
}
+4
View File
@@ -189,6 +189,7 @@ pub enum MobileShellState {
Ready,
Locked,
Error,
Stale,
}
impl From<StorageShellState> for MobileShellState {
@@ -199,6 +200,7 @@ impl From<StorageShellState> for MobileShellState {
StorageShellState::Ready => Self::Ready,
StorageShellState::Locked => Self::Locked,
StorageShellState::Error => Self::Error,
StorageShellState::Stale => Self::Stale,
}
}
}
@@ -211,6 +213,7 @@ impl From<MobileShellState> for StorageShellState {
MobileShellState::Ready => Self::Ready,
MobileShellState::Locked => Self::Locked,
MobileShellState::Error => Self::Error,
MobileShellState::Stale => Self::Stale,
}
}
}
@@ -2520,6 +2523,7 @@ mod tests {
MobileShellState::Ready,
MobileShellState::Locked,
MobileShellState::Error,
MobileShellState::Stale,
] {
let shell = super::mobile_shell_fixture(state);
assert_eq!(shell.selected_tab, MobileTab::Home);
+6
View File
@@ -105,6 +105,7 @@ pub enum MobileShellState {
Ready,
Locked,
Error,
Stale,
}
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -250,6 +251,10 @@ fn state_copy(tab: MobileTab, state: MobileShellState, error: Option<&str>) -> (
"IronStorage is Locked".to_owned(),
"Authenticate to reveal protected content.".to_owned(),
),
MobileShellState::Stale => (
"Password Store Changed".to_owned(),
"Refresh to load the current repository state.".to_owned(),
),
MobileShellState::Error => (
"IronStorage Is Unavailable".to_owned(),
error
@@ -275,6 +280,7 @@ mod tests {
MobileShellState::Ready,
MobileShellState::Locked,
MobileShellState::Error,
MobileShellState::Stale,
] {
let shell = MobileShell::fixture(state);
assert_eq!(shell.selected_tab(), MobileTab::Home);
+72
View File
@@ -0,0 +1,72 @@
# Adaptive Android shell
The single `MainActivity` is a `FragmentActivity` (a `ComponentActivity` subclass)
so AndroidX BiometricPrompt can retain its OS authentication fragment. Compose,
Hilt ViewModels, lifecycle-aware StateFlow collection, and Navigation 3 1.2.0
present the Rust-owned Home, Passwords, Search, TOTP, and Preferences pages.
The shell uses actual available window width: bottom navigation below 600 dp,
and a scrollable navigation rail at larger widths. Passwords retains Material 3
Adaptive list/detail panes and the library's fold/hinge-aware directives. Live
window changes recompute both layouts without replacing native handles or
repository identities. Large text uses labelled navigation icons on compact
windows, leaving accessible names and 48 dp or larger selectable touch targets.
Page headings, selected tab semantics, radio groups, and keyboard activation use
Compose semantics rather than display-string interpretation.
Navigation 3 renders each top-level root and Preferences' Appearance child.
Each destination retains its own navigation: Rust owns Passwords' directory,
entry, parent and revision; Preferences retains its child independently of tab
switches. Home and TOTP have root stacks, and Search hands a typed result to
Passwords. The TOTP root presents Rust shell availability; the TOTP list/detail
workflow is assigned to #107, discovery to #110, and full preferences to #104.
SavedStateHandle stores only the selected tab, Appearance child flag, and
Password directory/entry/revision identifiers. Navigation 3 deliberately has no
saveable screen-content decorator. Search text, passphrases, editors, decrypted
fields, codes, credentials and native handles never enter navigation saved
state. Existing Rust process-recreated authentication starts locked and checks
restored object revisions before opening an entry. Backgrounding does not renew
a lease. Leaving Passwords invalidates pending operations, clears protected
presentation, and revokes the lease without treating a tab switch as manual
search-history clearing. Navigation cancels setup, Home and Search work before switching destinations and
clears setup secrets before opening Appearance or another tab. Outgoing animated
entries do not cancel work belonging to the incoming destination; sequence/revision checks reject late native results. Cleanup registrations
use distinct owner tokens: removing a screen with the same surface callback cannot
remove another screen's protection. The unchanged immediate memory-pressure
masking assertion also covers setup destinations during screen replacement.
Appearance is read and written through Rust's existing mobile appearance API.
The shell's only decision is translating System/Light/Dark into Material color
schemes, using the OS preference for System. System-bar icon contrast follows the active
Material background while the activity remains edge-to-edge. Failed operations preserve the
last confirmed appearance. No Android preference store or domain policy is added.
Rust fixtures generate Loading, Empty, Ready, Locked, Error and Stale pages for
all five destinations; no repository or secret is needed to exercise them. Tests
cover initial async loading versus restored selection, stale completion rejection,
all fixture pages, compact/expanded navigation, large text, keyboard activation,
independent Preferences navigation and reconstruction from primitive saved state.
The native encrypted-store fixture checks appearance persistence and locked
restoration along with entry navigation and viewer operations.
Validation used ARM64 emulators on API 26 (4 KB), API 37 (4 KB), and API 37
(strict 16 KB), including app/core/bridge instrumentation and five cold
starts per device. Both supported ARM ABIs passed release packaging, native
linker, backup exclusion, architecture, dependency/license and SBOM gates.
All four Rust workspace gates passed.
Computer use in Android Studio's embedded emulator verified portrait bottom
navigation, landscape rail, Appearance navigation and light/dark/system changes.
TalkBack focus and activation were observed on Search and Preferences; text at
160% with 520 dpi display scaling retained accessible controls. Accessibility,
font and density settings were restored afterward. Instrumentation also exercises
keyboard activation and expanded navigation with 200% text. TalkBack audio was
not recorded; no fold-posture or desktop-window AVD was available, so those use
the adaptive library's directives without a claim of device-specific validation.
ARMv7 was built and packaged; the available live emulator images were ARM64.
The additive shared shell-state contract regenerated the hardened Swift bindings.
The iPhone and dependent watch simulator targets built after XcodeGen generation;
the iPhone app was installed over its existing container and computer use verified
locked folder navigation. Existing biometric enrollment and app data were retained.
+3 -2
View File
@@ -102,6 +102,7 @@ fn fixtures() -> String {
MobileShellState::Ready,
MobileShellState::Locked,
MobileShellState::Error,
MobileShellState::Stale,
] {
output.push_str(&format!(" ShellState.{state:?} to listOf(\n"));
for page in MobileShell::fixture(state).pages() {
@@ -252,8 +253,8 @@ mod tests {
fn fixtures_are_complete_non_secret_and_deterministic() {
let source = fixtures();
assert_eq!(source, fixtures());
assert_eq!(source.matches(" to listOf(").count(), 5);
assert_eq!(source.matches("ShellPage(").count(), 25);
assert_eq!(source.matches(" to listOf(").count(), 6);
assert_eq!(source.matches("ShellPage(").count(), 30);
assert!(!source.contains("Watch"));
assert_eq!(kotlin_string("a\"$b\nc"), "\"a\\\"\\$b\\u000ac\"");
assert_eq!(kotlin_string("\0\u{c}\\é"), "\"\\u0000\\u000c\\\\é\"");