Implement structured Android entry viewer (#99)
Android ARM verification / rust (push) Failing after 47m18s
Android ARM verification / android (push) Failing after 8s
Dependency security audit / rustsec (push) Successful in 22s

This commit is contained in:
hugo committed 2026-10-09 17:36:38 +02:00
1 parent 647dc1bb4b
commit c0ba6d43d8
29 files changed
+1768 -78

No files matched your search

+124 -7
View File
@@ -21,7 +21,7 @@ use crate::{
mobile_entry::{
MobileEntryDraft, MobileEntryEditorError, MobileEntryEditorFieldKind,
MobileEntryEditorInput, MobileEntryEditorPage, MobileEntryEditorSession, MobileEntryPage,
MobileEntryValueError, field_value,
MobileEntryValueError, document_revision, field_value,
},
mobile_mutation::{
MobileMutationAction, MobileMutationError, MobileMutationOutcome, MobileMutationPlan,
@@ -949,6 +949,98 @@ impl MobileAuthentication {
field_value(&document, field).map_err(value_error)
}
/// Field IDs are scoped to the exact document that the viewer presented.
pub fn reveal_presented_entry_field(
&self,
path: &str,
field_id: u64,
revision: &str,
) -> Result<String, MobileAuthenticationError> {
let document = self.open_presented_document(path, revision)?;
field_value(&document, field_id).map_err(value_error)
}
pub fn copy_presented_entry_field(
&self,
path: &str,
field_id: u64,
revision: &str,
) -> Result<MobileEntryCopy, MobileAuthenticationError> {
Ok(MobileEntryCopy {
value: self.reveal_presented_entry_field(path, field_id, revision)?,
timeout_seconds: self.config.clipboard_timeout().duration().as_secs(),
})
}
pub fn refresh_entry_presentation(
&self,
path: &str,
revision: &str,
unix_seconds: u64,
) -> Result<MobileEntryPresentation, MobileAuthenticationError> {
let presentation = self.entry_page(path, unix_seconds)?;
self.check_document_revision(presentation.page().revision(), revision)?;
Ok(presentation)
}
fn open_presented_document(
&self,
path: &str,
revision: &str,
) -> Result<EntryDocument, MobileAuthenticationError> {
let document = self.open_active_document(path)?;
self.check_document_revision(&document_revision(&document), revision)?;
Ok(document)
}
fn check_document_revision(
&self,
current: &str,
expected: &str,
) -> Result<(), MobileAuthenticationError> {
if current != expected {
self.manual_lock()?;
return Err(MobileAuthenticationError::new(
MobileAuthenticationErrorKind::Conflict,
"Entry Changed",
"Refresh and unlock the entry again before using its fields.",
));
}
Ok(())
}
pub fn copy_presented_totp_code(
&self,
path: &str,
revision: &str,
unix_seconds: u64,
) -> Result<MobileEntryCopy, MobileAuthenticationError> {
let presentation = self.refresh_entry_presentation(path, revision, unix_seconds)?;
let detail = presentation.totp().ok_or_else(|| {
entry_detail(
"TOTP Code Is Unavailable",
"The entry has no current TOTP code.",
)
})?;
Ok(MobileEntryCopy {
value: String::from_utf8(detail.code().expose().to_vec())
.map_err(|_| entry_detail("TOTP Code Is Unavailable", "The code is not UTF-8."))?,
timeout_seconds: self.config.clipboard_timeout().duration().as_secs(),
})
}
pub fn prepare_presented_entry_mutation(
&self,
path: &str,
revision: &str,
action: MobileMutationAction,
) -> Result<MobileMutationPlan, MobileAuthenticationError> {
self.open_presented_document(path, revision)?;
let plan = self.prepare_entry_mutation(path, action)?;
self.open_presented_document(path, revision)?;
Ok(plan)
}
pub fn copy_entry_field(
&self,
path: &str,
@@ -1234,17 +1326,42 @@ impl MobileAuthentication {
) -> Result<MobileEntryPage, MobileAuthenticationError> {
self.ensure_repository_idle()?;
let mut document = self.open_active_document(path)?;
self.save_replaced_entry_field(&mut document, field, value)
}
pub fn replace_presented_entry_field(
&self,
path: &str,
field_id: u64,
revision: &str,
value: String,
) -> Result<MobileEntryPage, MobileAuthenticationError> {
self.ensure_repository_idle()?;
let mut document = self.open_presented_document(path, revision)?;
self.save_replaced_entry_field(&mut document, field_id, value)
}
fn save_replaced_entry_field(
&self,
document: &mut EntryDocument,
field_id: u64,
value: String,
) -> Result<MobileEntryPage, MobileAuthenticationError> {
document
.replace_field_value(EntryFieldId::from_value(field), value.into_bytes())
.replace_field_value(EntryFieldId::from_value(field_id), value.into_bytes())
.map_err(document_error)?;
let identity = self.git_identity()?;
let mut committer = AutomaticEntryCommitter::for_entry(&self.repository, path, identity)
.map_err(|error| entry_detail("Password Entry Could Not Be Saved", error))?;
let mut committer = AutomaticEntryCommitter::for_entry(
&self.repository,
&document.path().to_string(),
identity,
)
.map_err(|error| entry_detail("Password Entry Could Not Be Saved", error))?;
EntryDocumentService::new(&self.repository, &self.keys)
.save_recoverable(&document, None, &mut committer)
.save_recoverable(document, None, &mut committer)
.map_err(document_error)?;
self.reconcile_saved_document(&document);
Ok(MobileEntryPage::from_document(&document))
self.reconcile_saved_document(document);
Ok(MobileEntryPage::from_document(document))
}
pub fn begin_entry_editor(