Implement iPhone and Apple Watch preferences (#53)
This commit is contained in:
@@ -5,6 +5,12 @@
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"filename" : "AppIcon.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "watchos",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -629,10 +629,16 @@ public protocol MobileAuthenticationProtocol: AnyObject, Sendable {
|
||||
|
||||
func manualLock() throws
|
||||
|
||||
func mobileAppearance() throws -> MobileAppearance
|
||||
|
||||
func performEntryMutation(request: MobileMutationRequest) throws -> MobileMutationOutcome
|
||||
|
||||
func preferences(watchSupported: Bool, watchPaired: Bool, watchAppInstalled: Bool) throws -> MobilePreferences
|
||||
|
||||
func prepareEntryMutation(path: String, action: MobileMutationAction) throws -> MobileMutationPlan
|
||||
|
||||
func removeApplicationToken() throws
|
||||
|
||||
func removeEntryEditorField(editor: UInt64, field: UInt64) throws -> MobileEntryEditorPage
|
||||
|
||||
func reorderEntryEditorField(editor: UInt64, field: UInt64, index: UInt32) throws -> MobileEntryEditorPage
|
||||
@@ -645,10 +651,14 @@ public protocol MobileAuthenticationProtocol: AnyObject, Sendable {
|
||||
|
||||
func searchCachedTotpPage(query: String) throws -> MobileTotpPage?
|
||||
|
||||
func setAuthenticationTimeout(seconds: UInt64) throws
|
||||
|
||||
func setBiometricUnlock(enabled: Bool) throws -> MobileAuthenticationState
|
||||
|
||||
func setGitIdentity(name: String, email: String) throws -> MobileGitIdentity
|
||||
|
||||
func setMobileAppearance(appearance: MobileAppearance) throws
|
||||
|
||||
func setTotpWatchShared(path: String, shared: Bool, unixSeconds: UInt64) throws -> MobileTotpDetail
|
||||
|
||||
func state() throws -> MobileAuthenticationState
|
||||
@@ -852,6 +862,15 @@ open func manualLock()throws {try rustCallWithError(FfiConverterTypeMobileAuth
|
||||
}
|
||||
}
|
||||
|
||||
open func mobileAppearance()throws -> MobileAppearance {
|
||||
return try FfiConverterTypeMobileAppearance_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||
uniffiCallStatus in
|
||||
uniffi_ironstorage_apple_fn_method_mobileauthentication_mobile_appearance(
|
||||
self.uniffiCloneHandle(),uniffiCallStatus
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
open func performEntryMutation(request: MobileMutationRequest)throws -> MobileMutationOutcome {
|
||||
return try FfiConverterTypeMobileMutationOutcome_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||
uniffiCallStatus in
|
||||
@@ -862,6 +881,18 @@ open func performEntryMutation(request: MobileMutationRequest)throws -> MobileM
|
||||
})
|
||||
}
|
||||
|
||||
open func preferences(watchSupported: Bool, watchPaired: Bool, watchAppInstalled: Bool)throws -> MobilePreferences {
|
||||
return try FfiConverterTypeMobilePreferences_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||
uniffiCallStatus in
|
||||
uniffi_ironstorage_apple_fn_method_mobileauthentication_preferences(
|
||||
self.uniffiCloneHandle(),
|
||||
FfiConverterBool.lower(watchSupported),
|
||||
FfiConverterBool.lower(watchPaired),
|
||||
FfiConverterBool.lower(watchAppInstalled),uniffiCallStatus
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
open func prepareEntryMutation(path: String, action: MobileMutationAction)throws -> MobileMutationPlan {
|
||||
return try FfiConverterTypeMobileMutationPlan_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||
uniffiCallStatus in
|
||||
@@ -873,6 +904,14 @@ open func prepareEntryMutation(path: String, action: MobileMutationAction)throws
|
||||
})
|
||||
}
|
||||
|
||||
open func removeApplicationToken()throws {try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||
uniffiCallStatus in
|
||||
uniffi_ironstorage_apple_fn_method_mobileauthentication_remove_application_token(
|
||||
self.uniffiCloneHandle(),uniffiCallStatus
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
open func removeEntryEditorField(editor: UInt64, field: UInt64)throws -> MobileEntryEditorPage {
|
||||
return try FfiConverterTypeMobileEntryEditorPage_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||
uniffiCallStatus in
|
||||
@@ -940,6 +979,15 @@ open func searchCachedTotpPage(query: String)throws -> MobileTotpPage? {
|
||||
})
|
||||
}
|
||||
|
||||
open func setAuthenticationTimeout(seconds: UInt64)throws {try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||
uniffiCallStatus in
|
||||
uniffi_ironstorage_apple_fn_method_mobileauthentication_set_authentication_timeout(
|
||||
self.uniffiCloneHandle(),
|
||||
FfiConverterUInt64.lower(seconds),uniffiCallStatus
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
open func setBiometricUnlock(enabled: Bool)throws -> MobileAuthenticationState {
|
||||
return try FfiConverterTypeMobileAuthenticationState_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||
uniffiCallStatus in
|
||||
@@ -961,6 +1009,15 @@ open func setGitIdentity(name: String, email: String)throws -> MobileGitIdentit
|
||||
})
|
||||
}
|
||||
|
||||
open func setMobileAppearance(appearance: MobileAppearance)throws {try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||
uniffiCallStatus in
|
||||
uniffi_ironstorage_apple_fn_method_mobileauthentication_set_mobile_appearance(
|
||||
self.uniffiCloneHandle(),
|
||||
FfiConverterTypeMobileAppearance_lower(appearance),uniffiCallStatus
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
open func setTotpWatchShared(path: String, shared: Bool, unixSeconds: UInt64)throws -> MobileTotpDetail {
|
||||
return try FfiConverterTypeMobileTotpDetail_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
|
||||
uniffiCallStatus in
|
||||
@@ -3962,6 +4019,104 @@ public func FfiConverterTypeMobilePasswordRow_lower(_ value: MobilePasswordRow)
|
||||
}
|
||||
|
||||
|
||||
public struct MobilePreferences: Equatable, Hashable {
|
||||
public var repositoryTitle: String
|
||||
public var repositoryUrl: String
|
||||
public var serverTitle: String
|
||||
public var serverIdentity: String
|
||||
public var applicationAccount: String?
|
||||
public var defaultKeyTitle: String
|
||||
public var defaultKeyFingerprint: String
|
||||
public var authenticationTimeoutSeconds: UInt64
|
||||
public var biometricUnlockEnabled: Bool
|
||||
public var appearance: MobileAppearance
|
||||
public var watchState: MobileWatchPreferenceState
|
||||
public var watchTitle: String
|
||||
public var watchDetail: String
|
||||
|
||||
// Default memberwise initializers are never public by default, so we
|
||||
// declare one manually.
|
||||
public init(repositoryTitle: String, repositoryUrl: String, serverTitle: String, serverIdentity: String, applicationAccount: String?, defaultKeyTitle: String, defaultKeyFingerprint: String, authenticationTimeoutSeconds: UInt64, biometricUnlockEnabled: Bool, appearance: MobileAppearance, watchState: MobileWatchPreferenceState, watchTitle: String, watchDetail: String) {
|
||||
self.repositoryTitle = repositoryTitle
|
||||
self.repositoryUrl = repositoryUrl
|
||||
self.serverTitle = serverTitle
|
||||
self.serverIdentity = serverIdentity
|
||||
self.applicationAccount = applicationAccount
|
||||
self.defaultKeyTitle = defaultKeyTitle
|
||||
self.defaultKeyFingerprint = defaultKeyFingerprint
|
||||
self.authenticationTimeoutSeconds = authenticationTimeoutSeconds
|
||||
self.biometricUnlockEnabled = biometricUnlockEnabled
|
||||
self.appearance = appearance
|
||||
self.watchState = watchState
|
||||
self.watchTitle = watchTitle
|
||||
self.watchDetail = watchDetail
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#if compiler(>=6)
|
||||
extension MobilePreferences: Sendable {}
|
||||
#endif
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public struct FfiConverterTypeMobilePreferences: FfiConverterRustBuffer {
|
||||
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobilePreferences {
|
||||
return
|
||||
try MobilePreferences(
|
||||
repositoryTitle: FfiConverterString.read(from: &buf),
|
||||
repositoryUrl: FfiConverterString.read(from: &buf),
|
||||
serverTitle: FfiConverterString.read(from: &buf),
|
||||
serverIdentity: FfiConverterString.read(from: &buf),
|
||||
applicationAccount: FfiConverterOptionString.read(from: &buf),
|
||||
defaultKeyTitle: FfiConverterString.read(from: &buf),
|
||||
defaultKeyFingerprint: FfiConverterString.read(from: &buf),
|
||||
authenticationTimeoutSeconds: FfiConverterUInt64.read(from: &buf),
|
||||
biometricUnlockEnabled: FfiConverterBool.read(from: &buf),
|
||||
appearance: FfiConverterTypeMobileAppearance.read(from: &buf),
|
||||
watchState: FfiConverterTypeMobileWatchPreferenceState.read(from: &buf),
|
||||
watchTitle: FfiConverterString.read(from: &buf),
|
||||
watchDetail: FfiConverterString.read(from: &buf)
|
||||
)
|
||||
}
|
||||
|
||||
public static func write(_ value: MobilePreferences, into buf: inout [UInt8]) {
|
||||
FfiConverterString.write(value.repositoryTitle, into: &buf)
|
||||
FfiConverterString.write(value.repositoryUrl, into: &buf)
|
||||
FfiConverterString.write(value.serverTitle, into: &buf)
|
||||
FfiConverterString.write(value.serverIdentity, into: &buf)
|
||||
FfiConverterOptionString.write(value.applicationAccount, into: &buf)
|
||||
FfiConverterString.write(value.defaultKeyTitle, into: &buf)
|
||||
FfiConverterString.write(value.defaultKeyFingerprint, into: &buf)
|
||||
FfiConverterUInt64.write(value.authenticationTimeoutSeconds, into: &buf)
|
||||
FfiConverterBool.write(value.biometricUnlockEnabled, into: &buf)
|
||||
FfiConverterTypeMobileAppearance.write(value.appearance, into: &buf)
|
||||
FfiConverterTypeMobileWatchPreferenceState.write(value.watchState, into: &buf)
|
||||
FfiConverterString.write(value.watchTitle, into: &buf)
|
||||
FfiConverterString.write(value.watchDetail, into: &buf)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobilePreferences_lift(_ buf: RustBuffer) throws -> MobilePreferences {
|
||||
return try FfiConverterTypeMobilePreferences.lift(buf)
|
||||
}
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobilePreferences_lower(_ value: MobilePreferences) -> RustBuffer {
|
||||
return FfiConverterTypeMobilePreferences.lower(value)
|
||||
}
|
||||
|
||||
|
||||
public struct MobileShell: Equatable, Hashable {
|
||||
public var selectedTab: MobileTab
|
||||
public var pages: [MobilePage]
|
||||
@@ -4351,6 +4506,79 @@ public func FfiConverterTypeMobileWatchSnapshotStatus_lower(_ value: MobileWatch
|
||||
|
||||
|
||||
|
||||
public enum MobileAppearance: Equatable, Hashable {
|
||||
|
||||
case system
|
||||
case light
|
||||
case dark
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#if compiler(>=6)
|
||||
extension MobileAppearance: Sendable {}
|
||||
#endif
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public struct FfiConverterTypeMobileAppearance: FfiConverterRustBuffer {
|
||||
typealias SwiftType = MobileAppearance
|
||||
|
||||
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileAppearance {
|
||||
let variant: Int32 = try readInt(&buf)
|
||||
switch variant {
|
||||
|
||||
case 1: return .system
|
||||
|
||||
case 2: return .light
|
||||
|
||||
case 3: return .dark
|
||||
|
||||
default: throw UniffiInternalError.unexpectedEnumCase
|
||||
}
|
||||
}
|
||||
|
||||
public static func write(_ value: MobileAppearance, into buf: inout [UInt8]) {
|
||||
switch value {
|
||||
|
||||
|
||||
case .system:
|
||||
writeInt(&buf, Int32(1))
|
||||
|
||||
|
||||
case .light:
|
||||
writeInt(&buf, Int32(2))
|
||||
|
||||
|
||||
case .dark:
|
||||
writeInt(&buf, Int32(3))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileAppearance_lift(_ buf: RustBuffer) throws -> MobileAppearance {
|
||||
return try FfiConverterTypeMobileAppearance.lift(buf)
|
||||
}
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileAppearance_lower(_ value: MobileAppearance) -> RustBuffer {
|
||||
return FfiConverterTypeMobileAppearance.lower(value)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public enum MobileAuthenticationErrorKind: Equatable, Hashable {
|
||||
|
||||
case passphraseRequired
|
||||
@@ -6383,6 +6611,93 @@ public func FfiConverterTypeMobileTotpDiscoveryPhase_lower(_ value: MobileTotpDi
|
||||
|
||||
|
||||
|
||||
public enum MobileWatchPreferenceState: Equatable, Hashable {
|
||||
|
||||
case unsupported
|
||||
case notPaired
|
||||
case appNotInstalled
|
||||
case ready
|
||||
case pending
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#if compiler(>=6)
|
||||
extension MobileWatchPreferenceState: Sendable {}
|
||||
#endif
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public struct FfiConverterTypeMobileWatchPreferenceState: FfiConverterRustBuffer {
|
||||
typealias SwiftType = MobileWatchPreferenceState
|
||||
|
||||
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileWatchPreferenceState {
|
||||
let variant: Int32 = try readInt(&buf)
|
||||
switch variant {
|
||||
|
||||
case 1: return .unsupported
|
||||
|
||||
case 2: return .notPaired
|
||||
|
||||
case 3: return .appNotInstalled
|
||||
|
||||
case 4: return .ready
|
||||
|
||||
case 5: return .pending
|
||||
|
||||
default: throw UniffiInternalError.unexpectedEnumCase
|
||||
}
|
||||
}
|
||||
|
||||
public static func write(_ value: MobileWatchPreferenceState, into buf: inout [UInt8]) {
|
||||
switch value {
|
||||
|
||||
|
||||
case .unsupported:
|
||||
writeInt(&buf, Int32(1))
|
||||
|
||||
|
||||
case .notPaired:
|
||||
writeInt(&buf, Int32(2))
|
||||
|
||||
|
||||
case .appNotInstalled:
|
||||
writeInt(&buf, Int32(3))
|
||||
|
||||
|
||||
case .ready:
|
||||
writeInt(&buf, Int32(4))
|
||||
|
||||
|
||||
case .pending:
|
||||
writeInt(&buf, Int32(5))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileWatchPreferenceState_lift(_ buf: RustBuffer) throws -> MobileWatchPreferenceState {
|
||||
return try FfiConverterTypeMobileWatchPreferenceState.lift(buf)
|
||||
}
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileWatchPreferenceState_lower(_ value: MobileWatchPreferenceState) -> RustBuffer {
|
||||
return FfiConverterTypeMobileWatchPreferenceState.lower(value)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public enum MobileWatchSnapshotState: Equatable, Hashable {
|
||||
|
||||
case unavailable
|
||||
@@ -7221,12 +7536,21 @@ private let initializationResult: InitializationResult = {
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_manual_lock() != 57220) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_mobile_appearance() != 3564) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_perform_entry_mutation() != 7053) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_preferences() != 16444) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_prepare_entry_mutation() != 6234) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_remove_application_token() != 11452) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_remove_entry_editor_field() != 12238) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
@@ -7245,12 +7569,18 @@ private let initializationResult: InitializationResult = {
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_search_cached_totp_page() != 13608) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_set_authentication_timeout() != 48083) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_set_biometric_unlock() != 9486) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_set_git_identity() != 62371) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_set_mobile_appearance() != 12133) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileauthentication_set_totp_watch_shared() != 57472) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
|
||||
@@ -318,16 +318,31 @@ RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_git_identity(
|
||||
void uniffi_ironstorage_apple_fn_method_mobileauthentication_manual_lock(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_MOBILE_APPEARANCE
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_MOBILE_APPEARANCE
|
||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_mobile_appearance(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_PERFORM_ENTRY_MUTATION
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_PERFORM_ENTRY_MUTATION
|
||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_perform_entry_mutation(uint64_t ptr, RustBuffer request, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_PREFERENCES
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_PREFERENCES
|
||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_preferences(uint64_t ptr, int8_t watch_supported, int8_t watch_paired, int8_t watch_app_installed, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_PREPARE_ENTRY_MUTATION
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_PREPARE_ENTRY_MUTATION
|
||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_prepare_entry_mutation(uint64_t ptr, RustBuffer path, RustBuffer action, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_REMOVE_APPLICATION_TOKEN
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_REMOVE_APPLICATION_TOKEN
|
||||
void uniffi_ironstorage_apple_fn_method_mobileauthentication_remove_application_token(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_REMOVE_ENTRY_EDITOR_FIELD
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_REMOVE_ENTRY_EDITOR_FIELD
|
||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_remove_entry_editor_field(uint64_t ptr, uint64_t editor, uint64_t field, RustCallStatus *_Nonnull out_status
|
||||
@@ -358,6 +373,11 @@ RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_save_entry_ed
|
||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_search_cached_totp_page(uint64_t ptr, RustBuffer query, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SET_AUTHENTICATION_TIMEOUT
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SET_AUTHENTICATION_TIMEOUT
|
||||
void uniffi_ironstorage_apple_fn_method_mobileauthentication_set_authentication_timeout(uint64_t ptr, uint64_t seconds, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SET_BIOMETRIC_UNLOCK
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SET_BIOMETRIC_UNLOCK
|
||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_set_biometric_unlock(uint64_t ptr, int8_t enabled, RustCallStatus *_Nonnull out_status
|
||||
@@ -368,6 +388,11 @@ RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_set_biometric
|
||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_set_git_identity(uint64_t ptr, RustBuffer name, RustBuffer email, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SET_MOBILE_APPEARANCE
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SET_MOBILE_APPEARANCE
|
||||
void uniffi_ironstorage_apple_fn_method_mobileauthentication_set_mobile_appearance(uint64_t ptr, RustBuffer appearance, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SET_TOTP_WATCH_SHARED
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEAUTHENTICATION_SET_TOTP_WATCH_SHARED
|
||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileauthentication_set_totp_watch_shared(uint64_t ptr, RustBuffer path, int8_t shared, uint64_t unix_seconds, RustCallStatus *_Nonnull out_status
|
||||
@@ -1026,18 +1051,36 @@ uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_git_ident
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_MANUAL_LOCK
|
||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_manual_lock(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_MOBILE_APPEARANCE
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_MOBILE_APPEARANCE
|
||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_mobile_appearance(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_PERFORM_ENTRY_MUTATION
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_PERFORM_ENTRY_MUTATION
|
||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_perform_entry_mutation(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_PREFERENCES
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_PREFERENCES
|
||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_preferences(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_PREPARE_ENTRY_MUTATION
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_PREPARE_ENTRY_MUTATION
|
||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_prepare_entry_mutation(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REMOVE_APPLICATION_TOKEN
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REMOVE_APPLICATION_TOKEN
|
||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_remove_application_token(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_REMOVE_ENTRY_EDITOR_FIELD
|
||||
@@ -1074,6 +1117,12 @@ uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_save_entr
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SEARCH_CACHED_TOTP_PAGE
|
||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_search_cached_totp_page(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SET_AUTHENTICATION_TIMEOUT
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SET_AUTHENTICATION_TIMEOUT
|
||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_set_authentication_timeout(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SET_BIOMETRIC_UNLOCK
|
||||
@@ -1086,6 +1135,12 @@ uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_set_biome
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SET_GIT_IDENTITY
|
||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_set_git_identity(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SET_MOBILE_APPEARANCE
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SET_MOBILE_APPEARANCE
|
||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileauthentication_set_mobile_appearance(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEAUTHENTICATION_SET_TOTP_WATCH_SHARED
|
||||
|
||||
@@ -2,6 +2,7 @@ import UIKit
|
||||
import AVFoundation
|
||||
import Vision
|
||||
import VisionKit
|
||||
import WatchConnectivity
|
||||
|
||||
extension Notification.Name {
|
||||
static let ironStorageLocalStoreDidChange = Notification.Name(
|
||||
@@ -107,6 +108,13 @@ private final class AppContext: NSObject, UITabBarControllerDelegate {
|
||||
}
|
||||
tabs.viewControllers = navigationControllers
|
||||
tabs.delegate = self
|
||||
if let appearance = try? authentication?.mobileAppearance() {
|
||||
tabs.overrideUserInterfaceStyle = switch appearance {
|
||||
case .system: .unspecified
|
||||
case .light: .light
|
||||
case .dark: .dark
|
||||
}
|
||||
}
|
||||
restoreSelectedTab()
|
||||
monitorAuthentication()
|
||||
return tabs
|
||||
@@ -844,13 +852,17 @@ private final class ShellViewController: UITableViewController, MobileTabRoot {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class PreferencesViewController: UITableViewController, MobileTabRoot {
|
||||
private final class PreferencesViewController: UITableViewController, MobileTabRoot,
|
||||
WCSessionDelegate
|
||||
{
|
||||
fileprivate let shellTab = MobileTab.preferences
|
||||
private var page: MobilePage
|
||||
private let authentication: MobileAuthentication?
|
||||
private let keyTransfer = try? mobileKeyTransfer()
|
||||
private var state: MobileAuthenticationState?
|
||||
private var preferences: MobilePreferences?
|
||||
private var gitIdentity: MobileGitIdentity?
|
||||
private var watchSession: WCSession?
|
||||
private var preferenceTask: Task<Void, Never>?
|
||||
private var loadTask: Task<Void, Never>?
|
||||
private var loadGeneration = 0
|
||||
@@ -859,6 +871,12 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
self.page = page
|
||||
self.authentication = authentication
|
||||
super.init(style: .insetGrouped)
|
||||
if WCSession.isSupported() {
|
||||
let session = WCSession.default
|
||||
watchSession = session
|
||||
session.delegate = self
|
||||
session.activate()
|
||||
}
|
||||
title = page.title
|
||||
navigationItem.largeTitleDisplayMode = .always
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
@@ -881,6 +899,7 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
}
|
||||
|
||||
deinit {
|
||||
watchSession?.delegate = nil
|
||||
preferenceTask?.cancel()
|
||||
loadTask?.cancel()
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
@@ -892,14 +911,18 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
}
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
page.state == .ready ? 4 : 0
|
||||
page.state == .ready ? 7 : 0
|
||||
}
|
||||
|
||||
override func tableView(
|
||||
_ tableView: UITableView,
|
||||
numberOfRowsInSection section: Int
|
||||
) -> Int {
|
||||
section == 1 ? 2 : (section == 3 ? 2 : 1)
|
||||
switch section {
|
||||
case 0: 2
|
||||
case 1, 2, 3: 3
|
||||
default: 1
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(
|
||||
@@ -907,10 +930,13 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
titleForHeaderInSection section: Int
|
||||
) -> String? {
|
||||
switch section {
|
||||
case 0: "Git Commit Identity"
|
||||
case 1: "GPG Key Transfer"
|
||||
case 2: "Secure Unlock"
|
||||
default: "Authentication Session"
|
||||
case 0: "Password Store"
|
||||
case 1: "Application Token"
|
||||
case 2: "GPG Key & Recovery"
|
||||
case 3: "Authentication"
|
||||
case 4: "Appearance"
|
||||
case 5: "Apple Watch"
|
||||
default: "Git Commit Identity"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -918,16 +944,22 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
_ tableView: UITableView,
|
||||
titleForFooterInSection section: Int
|
||||
) -> String? {
|
||||
if section == 0 {
|
||||
return "Used as the author for commits created by the iPhone app."
|
||||
switch section {
|
||||
case 0:
|
||||
"Non-secret repository and server identities from the shared configuration."
|
||||
case 1:
|
||||
"The application token is stored only in protected system storage and is never displayed."
|
||||
case 2:
|
||||
"Import provides initial or recovery key setup. Private-key transfers require explicit confirmation and passphrase validation."
|
||||
case 3:
|
||||
"Manual lock and inactivity expiry immediately revoke the shared Rust authentication lease."
|
||||
case 5:
|
||||
"Pairing is read from the device; TOTP selection and synchronization state come from Rust."
|
||||
case 6:
|
||||
"Used as the author for commits created by the iPhone app."
|
||||
default:
|
||||
nil
|
||||
}
|
||||
if section == 1 {
|
||||
return "Scan or display ASCII-armored GPG keys. Private-key transfers require explicit confirmation and passphrase validation."
|
||||
}
|
||||
if section == 2 {
|
||||
return "When enabled, the GPG passphrase is device-only, requires a device passcode, and is invalidated when enrolled biometrics change."
|
||||
}
|
||||
return "Manual lock and inactivity expiry immediately revoke the shared Rust authentication lease."
|
||||
}
|
||||
|
||||
override func tableView(
|
||||
@@ -936,24 +968,58 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
) -> UITableViewCell {
|
||||
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
|
||||
var content = cell.defaultContentConfiguration()
|
||||
if indexPath.section == 0 {
|
||||
content.image = UIImage(systemName: "person.crop.circle")
|
||||
content.text = gitIdentity?.name ?? "Git Commit Identity"
|
||||
content.secondaryText = gitIdentity?.email ?? "Set the commit author name and email"
|
||||
switch (indexPath.section, indexPath.row) {
|
||||
case (0, 0):
|
||||
content.image = UIImage(systemName: "externaldrive.fill")
|
||||
content.text = preferences?.repositoryTitle ?? "Password Store"
|
||||
content.secondaryText = preferences?.repositoryUrl ?? "Repository unavailable"
|
||||
cell.selectionStyle = .none
|
||||
case (0, 1):
|
||||
content.image = UIImage(systemName: "server.rack")
|
||||
content.text = preferences?.serverTitle ?? "HTTPS Server"
|
||||
content.secondaryText = preferences?.serverIdentity ?? "Server identity unavailable"
|
||||
cell.selectionStyle = .none
|
||||
case (1, 0):
|
||||
content.image = UIImage(systemName: "key.fill")
|
||||
content.text = preferences?.applicationAccount ?? "No Application Token"
|
||||
content.secondaryText = preferences?.applicationAccount == nil
|
||||
? "Git network operations require a replacement token"
|
||||
: "Token stored in protected system storage"
|
||||
cell.selectionStyle = .none
|
||||
case (1, 1):
|
||||
content.image = UIImage(systemName: "arrow.triangle.2.circlepath")
|
||||
content.text = "Replace Token"
|
||||
content.secondaryText = "Store a new HTTPS application token"
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
cell.isUserInteractionEnabled = authentication != nil
|
||||
cell.contentView.alpha = authentication == nil ? 0.45 : 1
|
||||
} else if indexPath.section == 1 {
|
||||
let importing = indexPath.row == 0
|
||||
case (1, 2):
|
||||
content.image = UIImage(systemName: "trash")
|
||||
content.text = "Remove Token"
|
||||
content.textProperties.color = .systemRed
|
||||
content.secondaryText = "Disable authenticated Git network operations"
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
cell.isUserInteractionEnabled = preferences?.applicationAccount != nil
|
||||
cell.contentView.alpha = preferences?.applicationAccount == nil ? 0.45 : 1
|
||||
case (2, 0):
|
||||
content.image = UIImage(systemName: "person.badge.key.fill")
|
||||
content.text = preferences?.defaultKeyTitle ?? "Default GPG Key"
|
||||
content.secondaryText = preferences?.defaultKeyFingerprint ?? "Key unavailable"
|
||||
cell.selectionStyle = .none
|
||||
case (2, 1), (2, 2):
|
||||
let importing = indexPath.row == 1
|
||||
content.image = UIImage(systemName: importing ? "qrcode.viewfinder" : "qrcode")
|
||||
content.text = importing ? "Import GPG Key" : "Export GPG Key"
|
||||
content.secondaryText = importing
|
||||
? "Scan one or more transfer QR codes"
|
||||
? "Scan an initial or recovery key transfer"
|
||||
: "Display public or private key armor"
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
cell.isUserInteractionEnabled = keyTransfer != nil
|
||||
cell.contentView.alpha = keyTransfer == nil ? 0.45 : 1
|
||||
} else if indexPath.section == 2 {
|
||||
case (3, 0):
|
||||
content.image = UIImage(systemName: "timer")
|
||||
content.text = "Inactivity Timeout"
|
||||
content.secondaryText = "\(preferences?.authenticationTimeoutSeconds ?? 0) seconds"
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
case (3, 1):
|
||||
content.image = UIImage(systemName: "faceid")
|
||||
content.text = "Biometric Unlock"
|
||||
content.secondaryText = state?.biometricUnlockEnabled == true
|
||||
@@ -966,37 +1032,51 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
toggle.accessibilityLabel = "Biometric Unlock"
|
||||
cell.accessoryView = toggle
|
||||
cell.selectionStyle = .none
|
||||
} else if indexPath.row == 0 {
|
||||
case (3, 2):
|
||||
let unlocked = state?.unlocked == true
|
||||
content.image = UIImage(systemName: unlocked ? "lock.open.fill" : "lock.fill")
|
||||
content.text = unlocked ? "Unlocked" : "Locked"
|
||||
content.secondaryText = unlocked
|
||||
? "Locks in \(state?.remainingSeconds ?? 0) seconds without activity"
|
||||
: "Protected content is masked"
|
||||
cell.selectionStyle = .none
|
||||
} else {
|
||||
content.image = UIImage(systemName: "lock.fill")
|
||||
content.text = "Lock Now"
|
||||
content.textProperties.color = .systemRed
|
||||
content.secondaryText = "Revoke all active authentication handles"
|
||||
content.secondaryText = unlocked
|
||||
? "Revoke all active authentication handles"
|
||||
: "IronStorage is already locked"
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
cell.isUserInteractionEnabled = unlocked
|
||||
cell.contentView.alpha = unlocked ? 1 : 0.45
|
||||
case (4, 0):
|
||||
content.image = UIImage(systemName: "circle.lefthalf.filled")
|
||||
content.text = "App Appearance"
|
||||
content.secondaryText = appearanceTitle(preferences?.appearance ?? .system)
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
case (5, 0):
|
||||
content.image = UIImage(systemName: watchImage(preferences?.watchState))
|
||||
content.text = preferences?.watchTitle ?? "Checking Apple Watch"
|
||||
content.secondaryText = preferences?.watchDetail ?? "Reading pairing state"
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
default:
|
||||
content.image = UIImage(systemName: "person.crop.circle")
|
||||
content.text = gitIdentity?.name ?? "Git Commit Identity"
|
||||
content.secondaryText = gitIdentity?.email ?? "Set the commit author name and email"
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
cell.isUserInteractionEnabled = state?.unlocked == true
|
||||
cell.contentView.alpha = state?.unlocked == true ? 1 : 0.45
|
||||
}
|
||||
content.secondaryTextProperties.numberOfLines = 0
|
||||
cell.contentConfiguration = content
|
||||
cell.accessibilityLabel = [content.text, content.secondaryText]
|
||||
.compactMap { $0 }
|
||||
.joined(separator: ", ")
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
if indexPath.section == 0 {
|
||||
editGitIdentity()
|
||||
return
|
||||
}
|
||||
if indexPath.section == 1 {
|
||||
switch (indexPath.section, indexPath.row) {
|
||||
case (1, 1):
|
||||
tokenUpdateRequested()
|
||||
case (1, 2):
|
||||
removeTokenRequested()
|
||||
case (2, 1), (2, 2):
|
||||
guard let keyTransfer else { return }
|
||||
if indexPath.row == 0 {
|
||||
if indexPath.row == 1 {
|
||||
requestKeyScanner(keyTransfer)
|
||||
} else {
|
||||
navigationController?.pushViewController(
|
||||
@@ -1004,9 +1084,23 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
animated: true
|
||||
)
|
||||
}
|
||||
return
|
||||
case (3, 0):
|
||||
editAuthenticationTimeout()
|
||||
case (3, 2):
|
||||
lockNow()
|
||||
case (4, 0):
|
||||
chooseAppearance()
|
||||
case (5, 0):
|
||||
openWatchPreference()
|
||||
case (6, 0):
|
||||
editGitIdentity()
|
||||
default:
|
||||
break
|
||||
}
|
||||
guard indexPath.section == 3, indexPath.row == 1, let authentication else { return }
|
||||
}
|
||||
|
||||
private func lockNow() {
|
||||
guard let authentication else { return }
|
||||
do {
|
||||
try authentication.manualLock()
|
||||
refreshState()
|
||||
@@ -1188,7 +1282,7 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
switch result {
|
||||
case let .success(identity):
|
||||
gitIdentity = identity
|
||||
tableView.reloadSections(IndexSet(integer: 0), with: .automatic)
|
||||
tableView.reloadSections(IndexSet(integer: 6), with: .automatic)
|
||||
UIAccessibility.post(
|
||||
notification: .announcement,
|
||||
argument: "Git commit identity updated"
|
||||
@@ -1199,6 +1293,147 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
}
|
||||
}
|
||||
|
||||
private func removeTokenRequested() {
|
||||
guard preferences?.applicationAccount != nil else { return }
|
||||
let alert = UIAlertController(
|
||||
title: "Remove Application Token?",
|
||||
message: "Local password browsing remains available, but Fetch, Pull, and Push will fail until a replacement token is stored.",
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||
alert.addAction(UIAlertAction(title: "Remove Token", style: .destructive) {
|
||||
[weak self] _ in
|
||||
self?.runPreferenceUpdate(announcement: "Application token removed") {
|
||||
try self?.authentication?.removeApplicationToken()
|
||||
}
|
||||
})
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
private func editAuthenticationTimeout() {
|
||||
let alert = UIAlertController(
|
||||
title: "Inactivity Timeout",
|
||||
message: "Enter a shared timeout from 1 to 86400 seconds.",
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addTextField { [preferences] field in
|
||||
field.keyboardType = .numberPad
|
||||
field.text = String(preferences?.authenticationTimeoutSeconds ?? 120)
|
||||
field.accessibilityLabel = "Timeout in seconds"
|
||||
}
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||
alert.addAction(UIAlertAction(title: "Save", style: .default) {
|
||||
[weak self, weak alert] _ in
|
||||
let seconds = UInt64(alert?.textFields?.first?.text ?? "") ?? 0
|
||||
self?.runPreferenceUpdate(announcement: "Inactivity timeout updated") {
|
||||
try self?.authentication?.setAuthenticationTimeout(seconds: seconds)
|
||||
}
|
||||
})
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
private func chooseAppearance() {
|
||||
let alert = UIAlertController(title: "App Appearance", message: nil, preferredStyle: .actionSheet)
|
||||
for appearance in [MobileAppearance.system, .light, .dark] {
|
||||
let selected = appearance == preferences?.appearance
|
||||
alert.addAction(UIAlertAction(
|
||||
title: "\(selected ? "✓ " : "")\(appearanceTitle(appearance))",
|
||||
style: .default
|
||||
) { [weak self] _ in
|
||||
self?.runPreferenceUpdate(announcement: "Appearance updated") {
|
||||
try self?.authentication?.setMobileAppearance(appearance: appearance)
|
||||
}
|
||||
})
|
||||
}
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||
alert.popoverPresentationController?.sourceView = tableView
|
||||
alert.popoverPresentationController?.sourceRect = tableView.bounds
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
private func runPreferenceUpdate(
|
||||
announcement: String,
|
||||
operation: @escaping @Sendable () throws -> Void
|
||||
) {
|
||||
preferenceTask?.cancel()
|
||||
preferenceTask = Task { [weak self] in
|
||||
let result = await Task.detached(priority: .userInitiated) {
|
||||
do {
|
||||
try operation()
|
||||
return Result<Void, AuthenticationFailure>.success(())
|
||||
} catch let error as MobileAuthenticationFfiError {
|
||||
return .failure(AuthenticationFailure(error))
|
||||
} catch {
|
||||
return .failure(.unexpected)
|
||||
}
|
||||
}.value
|
||||
guard !Task.isCancelled, let self else { return }
|
||||
switch result {
|
||||
case .success:
|
||||
refreshState()
|
||||
UIAccessibility.post(notification: .announcement, argument: announcement)
|
||||
case let .failure(failure):
|
||||
presentAuthenticationFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func openWatchPreference() {
|
||||
switch preferences?.watchState {
|
||||
case .ready, .pending:
|
||||
selectTotpTab()
|
||||
case .notPaired:
|
||||
presentWatchGuidance(
|
||||
title: "Pair an Apple Watch",
|
||||
message: "Open the Watch app on this iPhone and pair an Apple Watch before configuring IronStorage synchronization."
|
||||
)
|
||||
case .appNotInstalled:
|
||||
presentWatchGuidance(
|
||||
title: "Install IronStorage on Apple Watch",
|
||||
message: "Open the Watch app on this iPhone and install the IronStorage companion on the paired watch."
|
||||
)
|
||||
default:
|
||||
presentWatchGuidance(
|
||||
title: "Apple Watch Unavailable",
|
||||
message: "Apple Watch connectivity is unavailable on this device."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func presentWatchGuidance(title: String, message: String) {
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .default))
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
private func selectTotpTab() {
|
||||
guard let controllers = tabBarController?.viewControllers else { return }
|
||||
tabBarController?.selectedIndex = controllers.firstIndex { controller in
|
||||
guard
|
||||
let navigation = controller as? UINavigationController,
|
||||
let root = navigation.viewControllers.first as? MobileTabRoot
|
||||
else { return false }
|
||||
return root.shellTab == .totp
|
||||
} ?? tabBarController?.selectedIndex ?? 0
|
||||
}
|
||||
|
||||
private func appearanceTitle(_ appearance: MobileAppearance) -> String {
|
||||
switch appearance {
|
||||
case .system: "System"
|
||||
case .light: "Light"
|
||||
case .dark: "Dark"
|
||||
}
|
||||
}
|
||||
|
||||
private func watchImage(_ state: MobileWatchPreferenceState?) -> String {
|
||||
switch state {
|
||||
case .ready: "applewatch.radiowaves.left.and.right"
|
||||
case .pending: "arrow.triangle.2.circlepath"
|
||||
case .notPaired, .appNotInstalled: "applewatch.slash"
|
||||
default: "applewatch"
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func authenticationDidChange() {
|
||||
refreshState()
|
||||
}
|
||||
@@ -1244,8 +1479,38 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
private func refreshState() {
|
||||
state = try? authentication?.state()
|
||||
gitIdentity = try? authentication?.gitIdentity()
|
||||
preferences = try? authentication?.preferences(
|
||||
watchSupported: WCSession.isSupported(),
|
||||
watchPaired: watchSession?.isPaired ?? false,
|
||||
watchAppInstalled: watchSession?.isWatchAppInstalled ?? false
|
||||
)
|
||||
if let appearance = preferences?.appearance {
|
||||
tabBarController?.overrideUserInterfaceStyle = switch appearance {
|
||||
case .system: .unspecified
|
||||
case .light: .light
|
||||
case .dark: .dark
|
||||
}
|
||||
}
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
nonisolated func session(
|
||||
_ session: WCSession,
|
||||
activationDidCompleteWith activationState: WCSessionActivationState,
|
||||
error: Error?
|
||||
) {
|
||||
Task { @MainActor [weak self] in self?.refreshState() }
|
||||
}
|
||||
|
||||
nonisolated func sessionDidBecomeInactive(_ session: WCSession) {}
|
||||
|
||||
nonisolated func sessionDidDeactivate(_ session: WCSession) {
|
||||
session.activate()
|
||||
}
|
||||
|
||||
nonisolated func sessionWatchStateDidChange(_ session: WCSession) {
|
||||
Task { @MainActor [weak self] in self?.refreshState() }
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>WKApplication</key>
|
||||
<true/>
|
||||
<key>WKCompanionAppBundleIdentifier</key>
|
||||
<string>de.rfc1437.ironstorage</string>
|
||||
</dict>
|
||||
|
||||
@@ -31,6 +31,9 @@ targets:
|
||||
- Assets.xcassets
|
||||
- Sources/App
|
||||
- Generated/ironstorage_apple.swift
|
||||
dependencies:
|
||||
- target: IronStorageWatch
|
||||
embed: true
|
||||
preBuildScripts:
|
||||
- name: Build Rust core
|
||||
basedOnDependencyAnalysis: false
|
||||
@@ -78,6 +81,8 @@ targets:
|
||||
path: Watch-Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: IronStorage
|
||||
WKApplication: true
|
||||
WKCompanionAppBundleIdentifier: de.rfc1437.ironstorage
|
||||
sources:
|
||||
- Assets.xcassets
|
||||
- Sources/Watch
|
||||
|
||||
Reference in New Issue
Block a user