Build native iPhone application shell

This commit is contained in:
2026-08-11 15:12:26 +02:00
parent 64c90aa1d2
commit db898152bf
12 changed files with 1199 additions and 18 deletions

View File

@@ -3,17 +3,181 @@
//! Mechanical UniFFI exports for Apple presentation code.
use std::{error::Error, fmt};
use ironstorage::{
config::ConfigError,
mobile::{self, MobileShellState as StorageShellState, MobileTab as StorageTab},
};
uniffi::setup_scaffolding!();
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
pub enum MobileTab {
Home,
Passwords,
Totp,
Preferences,
}
impl From<StorageTab> for MobileTab {
fn from(tab: StorageTab) -> Self {
match tab {
StorageTab::Home => Self::Home,
StorageTab::Passwords => Self::Passwords,
StorageTab::Totp => Self::Totp,
StorageTab::Preferences => Self::Preferences,
}
}
}
impl From<MobileTab> for StorageTab {
fn from(tab: MobileTab) -> Self {
match tab {
MobileTab::Home => Self::Home,
MobileTab::Passwords => Self::Passwords,
MobileTab::Totp => Self::Totp,
MobileTab::Preferences => Self::Preferences,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
pub enum MobileShellState {
Loading,
Empty,
Ready,
Locked,
Error,
}
impl From<StorageShellState> for MobileShellState {
fn from(state: StorageShellState) -> Self {
match state {
StorageShellState::Loading => Self::Loading,
StorageShellState::Empty => Self::Empty,
StorageShellState::Ready => Self::Ready,
StorageShellState::Locked => Self::Locked,
StorageShellState::Error => Self::Error,
}
}
}
impl From<MobileShellState> for StorageShellState {
fn from(state: MobileShellState) -> Self {
match state {
MobileShellState::Loading => Self::Loading,
MobileShellState::Empty => Self::Empty,
MobileShellState::Ready => Self::Ready,
MobileShellState::Locked => Self::Locked,
MobileShellState::Error => Self::Error,
}
}
}
#[derive(Clone, uniffi::Record)]
pub struct MobilePage {
pub tab: MobileTab,
pub title: String,
pub system_image: String,
pub selected_system_image: String,
pub state: MobileShellState,
pub state_title: String,
pub state_detail: String,
}
#[derive(Clone, uniffi::Record)]
pub struct MobileShell {
pub selected_tab: MobileTab,
pub pages: Vec<MobilePage>,
}
impl From<mobile::MobileShell> for MobileShell {
fn from(shell: mobile::MobileShell) -> Self {
Self {
selected_tab: shell.selected_tab().into(),
pages: shell
.pages()
.iter()
.map(|page| MobilePage {
tab: page.tab().into(),
title: page.title().to_owned(),
system_image: page.system_image().to_owned(),
selected_system_image: page.selected_system_image().to_owned(),
state: page.state().into(),
state_title: page.state_title().to_owned(),
state_detail: page.state_detail().to_owned(),
})
.collect(),
}
}
}
#[derive(Debug, uniffi::Error)]
pub enum MobilePreferenceError {
Configuration { message: String },
}
impl fmt::Display for MobilePreferenceError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Configuration { message } => formatter.write_str(message),
}
}
}
impl Error for MobilePreferenceError {}
impl From<ConfigError> for MobilePreferenceError {
fn from(error: ConfigError) -> Self {
Self::Configuration {
message: error.to_string(),
}
}
}
#[uniffi::export]
pub fn product_name() -> String {
ironstorage::PRODUCT_NAME.to_owned()
}
#[uniffi::export]
pub fn mobile_shell() -> MobileShell {
mobile::MobileShell::load().into()
}
#[uniffi::export]
pub fn mobile_shell_fixture(state: MobileShellState) -> MobileShell {
mobile::MobileShell::fixture(state.into()).into()
}
#[uniffi::export]
pub fn set_selected_mobile_tab(tab: MobileTab) -> Result<(), MobilePreferenceError> {
mobile::store_selected_tab(tab.into()).map_err(Into::into)
}
#[cfg(test)]
mod tests {
use super::{MobileShellState, MobileTab};
#[test]
fn bridge_reads_product_name_from_storage_crate() {
assert_eq!(super::product_name(), ironstorage::PRODUCT_NAME);
}
#[test]
fn bridge_exposes_every_view_ready_shell_fixture() {
for state in [
MobileShellState::Loading,
MobileShellState::Empty,
MobileShellState::Ready,
MobileShellState::Locked,
MobileShellState::Error,
] {
let shell = super::mobile_shell_fixture(state);
assert_eq!(shell.selected_tab, MobileTab::Home);
assert_eq!(shell.pages.len(), 4);
assert!(shell.pages.iter().all(|page| page.state == state));
}
}
}