Show remote activity on iPhone Home

This commit is contained in:
2026-08-11 17:12:33 +02:00
parent daf122aff3
commit 8e883f5ccf
9 changed files with 3377 additions and 35 deletions

View File

@@ -8,6 +8,12 @@ use std::{error::Error, fmt, sync::Arc};
use ironstorage::{
config::ConfigError,
mobile::{self, MobileShellState as StorageShellState, MobileTab as StorageTab},
mobile_home::{
self, MobileHomeChangeKind as StorageHomeChangeKind,
MobileHomeChangeStatus as StorageHomeChangeStatus, MobileHomeError as StorageHomeError,
MobileHomeErrorKind as StorageHomeErrorKind, MobileHomeFreshness as StorageHomeFreshness,
MobileHomePhase as StorageHomePhase,
},
mobile_onboarding::{
self, MobileOnboardingError as StorageOnboardingError,
MobileOnboardingErrorKind as StorageOnboardingErrorKind,
@@ -97,6 +103,284 @@ pub struct MobileShell {
pub pages: Vec<MobilePage>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
pub enum MobileHomeFreshness {
NeverRefreshed,
Cached,
Current,
}
impl From<StorageHomeFreshness> for MobileHomeFreshness {
fn from(freshness: StorageHomeFreshness) -> Self {
match freshness {
StorageHomeFreshness::NeverRefreshed => Self::NeverRefreshed,
StorageHomeFreshness::Cached => Self::Cached,
StorageHomeFreshness::Current => Self::Current,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
pub enum MobileHomeChangeKind {
PasswordEntry,
RecipientPolicy,
RecipientSignature,
RepositoryFile,
}
impl From<StorageHomeChangeKind> for MobileHomeChangeKind {
fn from(kind: StorageHomeChangeKind) -> Self {
match kind {
StorageHomeChangeKind::PasswordEntry => Self::PasswordEntry,
StorageHomeChangeKind::RecipientPolicy => Self::RecipientPolicy,
StorageHomeChangeKind::RecipientSignature => Self::RecipientSignature,
StorageHomeChangeKind::RepositoryFile => Self::RepositoryFile,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
pub enum MobileHomeChangeStatus {
Added,
Modified,
Deleted,
}
impl From<StorageHomeChangeStatus> for MobileHomeChangeStatus {
fn from(status: StorageHomeChangeStatus) -> Self {
match status {
StorageHomeChangeStatus::Added => Self::Added,
StorageHomeChangeStatus::Modified => Self::Modified,
StorageHomeChangeStatus::Deleted => Self::Deleted,
}
}
}
#[derive(Clone, uniffi::Record)]
pub struct MobileHomeSummaryRow {
pub id: String,
pub title: String,
pub detail: String,
pub system_image: String,
}
#[derive(Clone, uniffi::Record)]
pub struct MobileHomeChange {
pub id: String,
pub title: String,
pub detail: String,
pub system_image: String,
pub kind: MobileHomeChangeKind,
pub status: MobileHomeChangeStatus,
}
#[derive(Clone, uniffi::Record)]
pub struct MobileHomeCommit {
pub id: String,
pub title: String,
pub detail: String,
pub system_image: String,
pub timestamp: i64,
pub changes: Vec<MobileHomeChange>,
}
#[derive(Clone, uniffi::Record)]
pub struct MobileHomeNotice {
pub title: String,
pub detail: String,
pub system_image: String,
}
#[derive(Clone, uniffi::Record)]
pub struct MobileHomePage {
pub freshness: MobileHomeFreshness,
pub refreshed_at: Option<i64>,
pub summaries: Vec<MobileHomeSummaryRow>,
pub incoming: Vec<MobileHomeCommit>,
pub outgoing: Vec<MobileHomeCommit>,
pub incoming_total: u32,
pub outgoing_total: u32,
pub notice: Option<MobileHomeNotice>,
}
impl From<mobile_home::MobileHomePage> for MobileHomePage {
fn from(page: mobile_home::MobileHomePage) -> Self {
Self {
freshness: page.freshness().into(),
refreshed_at: page.refreshed_at(),
summaries: page
.summaries()
.iter()
.map(|row| MobileHomeSummaryRow {
id: row.id().to_owned(),
title: row.title().to_owned(),
detail: row.detail().to_owned(),
system_image: row.system_image().to_owned(),
})
.collect(),
incoming: page.incoming().iter().map(mobile_home_commit).collect(),
outgoing: page.outgoing().iter().map(mobile_home_commit).collect(),
incoming_total: u32::try_from(page.incoming_total()).unwrap_or(u32::MAX),
outgoing_total: u32::try_from(page.outgoing_total()).unwrap_or(u32::MAX),
notice: page.notice().map(|notice| MobileHomeNotice {
title: notice.title().to_owned(),
detail: notice.detail().to_owned(),
system_image: notice.system_image().to_owned(),
}),
}
}
}
fn mobile_home_commit(commit: &mobile_home::MobileHomeCommit) -> MobileHomeCommit {
MobileHomeCommit {
id: commit.id().to_owned(),
title: commit.title().to_owned(),
detail: commit.detail().to_owned(),
system_image: commit.system_image().to_owned(),
timestamp: commit.timestamp(),
changes: commit
.changes()
.iter()
.map(|change| MobileHomeChange {
id: change.id().to_owned(),
title: change.title().to_owned(),
detail: change.detail().to_owned(),
system_image: change.system_image().to_owned(),
kind: change.kind().into(),
status: change.status().into(),
})
.collect(),
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
pub enum MobileHomePhase {
Validating,
Authenticating,
Receiving,
Integrating,
Finishing,
}
impl From<StorageHomePhase> for MobileHomePhase {
fn from(phase: StorageHomePhase) -> Self {
match phase {
StorageHomePhase::Validating => Self::Validating,
StorageHomePhase::Authenticating => Self::Authenticating,
StorageHomePhase::Receiving => Self::Receiving,
StorageHomePhase::Integrating => Self::Integrating,
StorageHomePhase::Finishing => Self::Finishing,
}
}
}
#[derive(Clone, uniffi::Record)]
pub struct MobileHomeProgress {
pub phase: MobileHomePhase,
pub title: String,
pub detail: String,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
pub enum MobileHomeErrorKind {
MissingConfiguration,
Configuration,
Authentication,
Conflict,
DirtyLocalChanges,
Offline,
Interrupted,
SecureStorage,
Repository,
PartialProgress,
}
impl From<StorageHomeErrorKind> for MobileHomeErrorKind {
fn from(kind: StorageHomeErrorKind) -> Self {
match kind {
StorageHomeErrorKind::MissingConfiguration => Self::MissingConfiguration,
StorageHomeErrorKind::Configuration => Self::Configuration,
StorageHomeErrorKind::Authentication => Self::Authentication,
StorageHomeErrorKind::Conflict => Self::Conflict,
StorageHomeErrorKind::DirtyLocalChanges => Self::DirtyLocalChanges,
StorageHomeErrorKind::Offline => Self::Offline,
StorageHomeErrorKind::Interrupted => Self::Interrupted,
StorageHomeErrorKind::SecureStorage => Self::SecureStorage,
StorageHomeErrorKind::Repository => Self::Repository,
StorageHomeErrorKind::PartialProgress => Self::PartialProgress,
}
}
}
#[derive(Debug, uniffi::Error)]
pub enum MobileHomeFfiError {
Failed {
kind: MobileHomeErrorKind,
title: String,
detail: String,
},
}
impl fmt::Display for MobileHomeFfiError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Failed { title, detail, .. } => write!(formatter, "{title}: {detail}"),
}
}
}
impl Error for MobileHomeFfiError {}
impl From<StorageHomeError> for MobileHomeFfiError {
fn from(error: StorageHomeError) -> Self {
Self::Failed {
kind: error.kind().into(),
title: error.title().to_owned(),
detail: error.detail().to_owned(),
}
}
}
#[derive(uniffi::Object)]
pub struct MobileHomeOperation {
operation: mobile_home::MobileHomeOperation,
}
#[uniffi::export]
impl MobileHomeOperation {
pub fn progress(&self) -> MobileHomeProgress {
let progress = self.operation.progress();
MobileHomeProgress {
phase: progress.phase().into(),
title: progress.title().to_owned(),
detail: progress.detail().to_owned(),
}
}
pub fn cached(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
self.operation.cached().map(Into::into).map_err(Into::into)
}
pub fn refresh_if_stale(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
self.operation
.refresh_if_stale()
.map(Into::into)
.map_err(Into::into)
}
pub fn refresh(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
self.operation.refresh().map(Into::into).map_err(Into::into)
}
pub fn pull(&self) -> Result<MobileHomePage, MobileHomeFfiError> {
self.operation.pull().map(Into::into).map_err(Into::into)
}
pub fn cancel(&self) {
self.operation.cancel();
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
pub enum MobileOnboardingPhase {
Validating,
@@ -301,6 +585,13 @@ pub fn set_selected_mobile_tab(tab: MobileTab) -> Result<(), MobilePreferenceErr
mobile::store_selected_tab(tab.into()).map_err(Into::into)
}
#[uniffi::export]
pub fn mobile_home_operation() -> Arc<MobileHomeOperation> {
Arc::new(MobileHomeOperation {
operation: mobile_home::MobileHomeOperation::default(),
})
}
#[uniffi::export]
pub fn mobile_onboarding_operation(
server_url: String,
@@ -333,7 +624,10 @@ pub fn replace_configured_mobile_application_token(
#[cfg(test)]
mod tests {
use super::{MobileOnboardingErrorKind, MobileOnboardingFfiError, MobileShellState, MobileTab};
use super::{
MobileHomePhase, MobileOnboardingErrorKind, MobileOnboardingFfiError, MobileShellState,
MobileTab,
};
#[test]
fn bridge_reads_product_name_from_storage_crate() {
@@ -374,4 +668,12 @@ mod tests {
}
}
}
#[test]
fn home_operation_exposes_typed_progress_and_cancellation() {
let operation = super::mobile_home_operation();
assert_eq!(operation.progress().phase, MobileHomePhase::Validating);
operation.cancel();
assert_eq!(operation.progress().phase, MobileHomePhase::Validating);
}
}