Onboard iPhone password-store clone
This commit is contained in:
@@ -462,6 +462,46 @@ fileprivate final class UniffiHandleMap<T>: @unchecked Sendable {
|
||||
// Public interface members begin here.
|
||||
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
fileprivate struct FfiConverterUInt32: FfiConverterPrimitive {
|
||||
typealias FfiType = UInt32
|
||||
typealias SwiftType = UInt32
|
||||
|
||||
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt32 {
|
||||
return try lift(readInt(&buf))
|
||||
}
|
||||
|
||||
public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
|
||||
writeInt(&buf, lower(value))
|
||||
}
|
||||
}
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
fileprivate struct FfiConverterBool : FfiConverter {
|
||||
typealias FfiType = Int8
|
||||
typealias SwiftType = Bool
|
||||
|
||||
public static func lift(_ value: Int8) throws -> Bool {
|
||||
return value != 0
|
||||
}
|
||||
|
||||
public static func lower(_ value: Bool) -> Int8 {
|
||||
return value ? 1 : 0
|
||||
}
|
||||
|
||||
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bool {
|
||||
return try lift(readInt(&buf))
|
||||
}
|
||||
|
||||
public static func write(_ value: Bool, into buf: inout [UInt8]) {
|
||||
writeInt(&buf, lower(value))
|
||||
}
|
||||
}
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
@@ -509,6 +549,323 @@ fileprivate struct FfiConverterString: FfiConverter {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public protocol MobileOnboardingOperationProtocol: AnyObject, Sendable {
|
||||
|
||||
func cancel()
|
||||
|
||||
func discover() throws -> MobileOnboardingDiscovery
|
||||
|
||||
func progress() -> MobileOnboardingProgress
|
||||
|
||||
func setup(branch: String, useExisting: Bool) throws -> MobileOnboardingOutcome
|
||||
|
||||
}
|
||||
open class MobileOnboardingOperation: MobileOnboardingOperationProtocol, @unchecked Sendable {
|
||||
fileprivate let handle: UInt64
|
||||
|
||||
/// Used to instantiate a [FFIObject] without an actual handle, for fakes in tests, mostly.
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public struct NoHandle {
|
||||
public init() {}
|
||||
}
|
||||
|
||||
// TODO: We'd like this to be `private` but for Swifty reasons,
|
||||
// we can't implement `FfiConverter` without making this `required` and we can't
|
||||
// make it `required` without making it `public`.
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
required public init(unsafeFromHandle handle: UInt64) {
|
||||
self.handle = handle
|
||||
}
|
||||
|
||||
// This constructor can be used to instantiate a fake object.
|
||||
// - Parameter noHandle: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject].
|
||||
//
|
||||
// - Warning:
|
||||
// Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing handle the FFI lower functions will crash.
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public init(noHandle: NoHandle) {
|
||||
self.handle = 0
|
||||
}
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func uniffiCloneHandle() -> UInt64 {
|
||||
return try! rustCall { uniffi_ironstorage_apple_fn_clone_mobileonboardingoperation(self.handle, $0) }
|
||||
}
|
||||
// No primary constructor declared for this class.
|
||||
|
||||
deinit {
|
||||
if handle == 0 {
|
||||
// Mock objects have handle=0 don't try to free them
|
||||
return
|
||||
}
|
||||
|
||||
try! rustCall { uniffi_ironstorage_apple_fn_free_mobileonboardingoperation(handle, $0) }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
open func cancel() {try! rustCall() {
|
||||
uniffiCallStatus in
|
||||
uniffi_ironstorage_apple_fn_method_mobileonboardingoperation_cancel(
|
||||
self.uniffiCloneHandle(),uniffiCallStatus
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
open func discover()throws -> MobileOnboardingDiscovery {
|
||||
return try FfiConverterTypeMobileOnboardingDiscovery_lift(try rustCallWithError(FfiConverterTypeMobileOnboardingFfiError_lift) {
|
||||
uniffiCallStatus in
|
||||
uniffi_ironstorage_apple_fn_method_mobileonboardingoperation_discover(
|
||||
self.uniffiCloneHandle(),uniffiCallStatus
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
open func progress() -> MobileOnboardingProgress {
|
||||
return try! FfiConverterTypeMobileOnboardingProgress_lift(try! rustCall() {
|
||||
uniffiCallStatus in
|
||||
uniffi_ironstorage_apple_fn_method_mobileonboardingoperation_progress(
|
||||
self.uniffiCloneHandle(),uniffiCallStatus
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
open func setup(branch: String, useExisting: Bool)throws -> MobileOnboardingOutcome {
|
||||
return try FfiConverterTypeMobileOnboardingOutcome_lift(try rustCallWithError(FfiConverterTypeMobileOnboardingFfiError_lift) {
|
||||
uniffiCallStatus in
|
||||
uniffi_ironstorage_apple_fn_method_mobileonboardingoperation_setup(
|
||||
self.uniffiCloneHandle(),
|
||||
FfiConverterString.lower(branch),
|
||||
FfiConverterBool.lower(useExisting),uniffiCallStatus
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public struct FfiConverterTypeMobileOnboardingOperation: FfiConverter {
|
||||
typealias FfiType = UInt64
|
||||
typealias SwiftType = MobileOnboardingOperation
|
||||
|
||||
public static func lift(_ handle: UInt64) throws -> MobileOnboardingOperation {
|
||||
return MobileOnboardingOperation(unsafeFromHandle: handle)
|
||||
}
|
||||
|
||||
public static func lower(_ value: MobileOnboardingOperation) -> UInt64 {
|
||||
return value.uniffiCloneHandle()
|
||||
}
|
||||
|
||||
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileOnboardingOperation {
|
||||
let handle: UInt64 = try readInt(&buf)
|
||||
return try lift(handle)
|
||||
}
|
||||
|
||||
public static func write(_ value: MobileOnboardingOperation, into buf: inout [UInt8]) {
|
||||
writeInt(&buf, lower(value))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileOnboardingOperation_lift(_ handle: UInt64) throws -> MobileOnboardingOperation {
|
||||
return try FfiConverterTypeMobileOnboardingOperation.lift(handle)
|
||||
}
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileOnboardingOperation_lower(_ value: MobileOnboardingOperation) -> UInt64 {
|
||||
return FfiConverterTypeMobileOnboardingOperation.lower(value)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public struct MobileOnboardingDiscovery: Equatable, Hashable {
|
||||
public var branches: [String]
|
||||
public var selectedBranch: UInt32
|
||||
|
||||
// Default memberwise initializers are never public by default, so we
|
||||
// declare one manually.
|
||||
public init(branches: [String], selectedBranch: UInt32) {
|
||||
self.branches = branches
|
||||
self.selectedBranch = selectedBranch
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#if compiler(>=6)
|
||||
extension MobileOnboardingDiscovery: Sendable {}
|
||||
#endif
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public struct FfiConverterTypeMobileOnboardingDiscovery: FfiConverterRustBuffer {
|
||||
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileOnboardingDiscovery {
|
||||
return
|
||||
try MobileOnboardingDiscovery(
|
||||
branches: FfiConverterSequenceString.read(from: &buf),
|
||||
selectedBranch: FfiConverterUInt32.read(from: &buf)
|
||||
)
|
||||
}
|
||||
|
||||
public static func write(_ value: MobileOnboardingDiscovery, into buf: inout [UInt8]) {
|
||||
FfiConverterSequenceString.write(value.branches, into: &buf)
|
||||
FfiConverterUInt32.write(value.selectedBranch, into: &buf)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileOnboardingDiscovery_lift(_ buf: RustBuffer) throws -> MobileOnboardingDiscovery {
|
||||
return try FfiConverterTypeMobileOnboardingDiscovery.lift(buf)
|
||||
}
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileOnboardingDiscovery_lower(_ value: MobileOnboardingDiscovery) -> RustBuffer {
|
||||
return FfiConverterTypeMobileOnboardingDiscovery.lower(value)
|
||||
}
|
||||
|
||||
|
||||
public struct MobileOnboardingOutcome: Equatable, Hashable {
|
||||
public var title: String
|
||||
public var detail: String
|
||||
|
||||
// Default memberwise initializers are never public by default, so we
|
||||
// declare one manually.
|
||||
public init(title: String, detail: String) {
|
||||
self.title = title
|
||||
self.detail = detail
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#if compiler(>=6)
|
||||
extension MobileOnboardingOutcome: Sendable {}
|
||||
#endif
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public struct FfiConverterTypeMobileOnboardingOutcome: FfiConverterRustBuffer {
|
||||
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileOnboardingOutcome {
|
||||
return
|
||||
try MobileOnboardingOutcome(
|
||||
title: FfiConverterString.read(from: &buf),
|
||||
detail: FfiConverterString.read(from: &buf)
|
||||
)
|
||||
}
|
||||
|
||||
public static func write(_ value: MobileOnboardingOutcome, into buf: inout [UInt8]) {
|
||||
FfiConverterString.write(value.title, into: &buf)
|
||||
FfiConverterString.write(value.detail, into: &buf)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileOnboardingOutcome_lift(_ buf: RustBuffer) throws -> MobileOnboardingOutcome {
|
||||
return try FfiConverterTypeMobileOnboardingOutcome.lift(buf)
|
||||
}
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileOnboardingOutcome_lower(_ value: MobileOnboardingOutcome) -> RustBuffer {
|
||||
return FfiConverterTypeMobileOnboardingOutcome.lower(value)
|
||||
}
|
||||
|
||||
|
||||
public struct MobileOnboardingProgress: Equatable, Hashable {
|
||||
public var phase: MobileOnboardingPhase
|
||||
public var title: String
|
||||
public var detail: String
|
||||
|
||||
// Default memberwise initializers are never public by default, so we
|
||||
// declare one manually.
|
||||
public init(phase: MobileOnboardingPhase, title: String, detail: String) {
|
||||
self.phase = phase
|
||||
self.title = title
|
||||
self.detail = detail
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#if compiler(>=6)
|
||||
extension MobileOnboardingProgress: Sendable {}
|
||||
#endif
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public struct FfiConverterTypeMobileOnboardingProgress: FfiConverterRustBuffer {
|
||||
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileOnboardingProgress {
|
||||
return
|
||||
try MobileOnboardingProgress(
|
||||
phase: FfiConverterTypeMobileOnboardingPhase.read(from: &buf),
|
||||
title: FfiConverterString.read(from: &buf),
|
||||
detail: FfiConverterString.read(from: &buf)
|
||||
)
|
||||
}
|
||||
|
||||
public static func write(_ value: MobileOnboardingProgress, into buf: inout [UInt8]) {
|
||||
FfiConverterTypeMobileOnboardingPhase.write(value.phase, into: &buf)
|
||||
FfiConverterString.write(value.title, into: &buf)
|
||||
FfiConverterString.write(value.detail, into: &buf)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileOnboardingProgress_lift(_ buf: RustBuffer) throws -> MobileOnboardingProgress {
|
||||
return try FfiConverterTypeMobileOnboardingProgress.lift(buf)
|
||||
}
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileOnboardingProgress_lower(_ value: MobileOnboardingProgress) -> RustBuffer {
|
||||
return FfiConverterTypeMobileOnboardingProgress.lower(value)
|
||||
}
|
||||
|
||||
|
||||
public struct MobilePage: Equatable, Hashable {
|
||||
public var tab: MobileTab
|
||||
public var title: String
|
||||
@@ -637,6 +994,287 @@ public func FfiConverterTypeMobileShell_lower(_ value: MobileShell) -> RustBuffe
|
||||
}
|
||||
|
||||
|
||||
|
||||
public enum MobileOnboardingErrorKind: Equatable, Hashable {
|
||||
|
||||
case invalidInput
|
||||
case unsupportedRemote
|
||||
case authentication
|
||||
case repository
|
||||
case existingClone
|
||||
case interrupted
|
||||
case secureStorage
|
||||
case configuration
|
||||
case alreadyConfigured
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#if compiler(>=6)
|
||||
extension MobileOnboardingErrorKind: Sendable {}
|
||||
#endif
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public struct FfiConverterTypeMobileOnboardingErrorKind: FfiConverterRustBuffer {
|
||||
typealias SwiftType = MobileOnboardingErrorKind
|
||||
|
||||
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileOnboardingErrorKind {
|
||||
let variant: Int32 = try readInt(&buf)
|
||||
switch variant {
|
||||
|
||||
case 1: return .invalidInput
|
||||
|
||||
case 2: return .unsupportedRemote
|
||||
|
||||
case 3: return .authentication
|
||||
|
||||
case 4: return .repository
|
||||
|
||||
case 5: return .existingClone
|
||||
|
||||
case 6: return .interrupted
|
||||
|
||||
case 7: return .secureStorage
|
||||
|
||||
case 8: return .configuration
|
||||
|
||||
case 9: return .alreadyConfigured
|
||||
|
||||
default: throw UniffiInternalError.unexpectedEnumCase
|
||||
}
|
||||
}
|
||||
|
||||
public static func write(_ value: MobileOnboardingErrorKind, into buf: inout [UInt8]) {
|
||||
switch value {
|
||||
|
||||
|
||||
case .invalidInput:
|
||||
writeInt(&buf, Int32(1))
|
||||
|
||||
|
||||
case .unsupportedRemote:
|
||||
writeInt(&buf, Int32(2))
|
||||
|
||||
|
||||
case .authentication:
|
||||
writeInt(&buf, Int32(3))
|
||||
|
||||
|
||||
case .repository:
|
||||
writeInt(&buf, Int32(4))
|
||||
|
||||
|
||||
case .existingClone:
|
||||
writeInt(&buf, Int32(5))
|
||||
|
||||
|
||||
case .interrupted:
|
||||
writeInt(&buf, Int32(6))
|
||||
|
||||
|
||||
case .secureStorage:
|
||||
writeInt(&buf, Int32(7))
|
||||
|
||||
|
||||
case .configuration:
|
||||
writeInt(&buf, Int32(8))
|
||||
|
||||
|
||||
case .alreadyConfigured:
|
||||
writeInt(&buf, Int32(9))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileOnboardingErrorKind_lift(_ buf: RustBuffer) throws -> MobileOnboardingErrorKind {
|
||||
return try FfiConverterTypeMobileOnboardingErrorKind.lift(buf)
|
||||
}
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileOnboardingErrorKind_lower(_ value: MobileOnboardingErrorKind) -> RustBuffer {
|
||||
return FfiConverterTypeMobileOnboardingErrorKind.lower(value)
|
||||
}
|
||||
|
||||
|
||||
|
||||
public
|
||||
enum MobileOnboardingFfiError: Swift.Error, Equatable, Hashable, Foundation.LocalizedError {
|
||||
|
||||
|
||||
|
||||
case Failed(kind: MobileOnboardingErrorKind, title: String, detail: String
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public var errorDescription: String? {
|
||||
String(reflecting: self)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if compiler(>=6)
|
||||
extension MobileOnboardingFfiError: Sendable {}
|
||||
#endif
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public struct FfiConverterTypeMobileOnboardingFfiError: FfiConverterRustBuffer {
|
||||
typealias SwiftType = MobileOnboardingFfiError
|
||||
|
||||
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileOnboardingFfiError {
|
||||
let variant: Int32 = try readInt(&buf)
|
||||
switch variant {
|
||||
|
||||
|
||||
|
||||
|
||||
case 1: return .Failed(
|
||||
kind: try FfiConverterTypeMobileOnboardingErrorKind.read(from: &buf),
|
||||
title: try FfiConverterString.read(from: &buf),
|
||||
detail: try FfiConverterString.read(from: &buf)
|
||||
)
|
||||
|
||||
default: throw UniffiInternalError.unexpectedEnumCase
|
||||
}
|
||||
}
|
||||
|
||||
public static func write(_ value: MobileOnboardingFfiError, into buf: inout [UInt8]) {
|
||||
switch value {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
case let .Failed(kind,title,detail):
|
||||
writeInt(&buf, Int32(1))
|
||||
FfiConverterTypeMobileOnboardingErrorKind.write(kind, into: &buf)
|
||||
FfiConverterString.write(title, into: &buf)
|
||||
FfiConverterString.write(detail, into: &buf)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileOnboardingFfiError_lift(_ buf: RustBuffer) throws -> MobileOnboardingFfiError {
|
||||
return try FfiConverterTypeMobileOnboardingFfiError.lift(buf)
|
||||
}
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileOnboardingFfiError_lower(_ value: MobileOnboardingFfiError) -> RustBuffer {
|
||||
return FfiConverterTypeMobileOnboardingFfiError.lower(value)
|
||||
}
|
||||
|
||||
|
||||
|
||||
public enum MobileOnboardingPhase: Equatable, Hashable {
|
||||
|
||||
case validating
|
||||
case authenticating
|
||||
case receiving
|
||||
case integrating
|
||||
case finishing
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#if compiler(>=6)
|
||||
extension MobileOnboardingPhase: Sendable {}
|
||||
#endif
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public struct FfiConverterTypeMobileOnboardingPhase: FfiConverterRustBuffer {
|
||||
typealias SwiftType = MobileOnboardingPhase
|
||||
|
||||
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileOnboardingPhase {
|
||||
let variant: Int32 = try readInt(&buf)
|
||||
switch variant {
|
||||
|
||||
case 1: return .validating
|
||||
|
||||
case 2: return .authenticating
|
||||
|
||||
case 3: return .receiving
|
||||
|
||||
case 4: return .integrating
|
||||
|
||||
case 5: return .finishing
|
||||
|
||||
default: throw UniffiInternalError.unexpectedEnumCase
|
||||
}
|
||||
}
|
||||
|
||||
public static func write(_ value: MobileOnboardingPhase, into buf: inout [UInt8]) {
|
||||
switch value {
|
||||
|
||||
|
||||
case .validating:
|
||||
writeInt(&buf, Int32(1))
|
||||
|
||||
|
||||
case .authenticating:
|
||||
writeInt(&buf, Int32(2))
|
||||
|
||||
|
||||
case .receiving:
|
||||
writeInt(&buf, Int32(3))
|
||||
|
||||
|
||||
case .integrating:
|
||||
writeInt(&buf, Int32(4))
|
||||
|
||||
|
||||
case .finishing:
|
||||
writeInt(&buf, Int32(5))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileOnboardingPhase_lift(_ buf: RustBuffer) throws -> MobileOnboardingPhase {
|
||||
return try FfiConverterTypeMobileOnboardingPhase.lift(buf)
|
||||
}
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
public func FfiConverterTypeMobileOnboardingPhase_lower(_ value: MobileOnboardingPhase) -> RustBuffer {
|
||||
return FfiConverterTypeMobileOnboardingPhase.lower(value)
|
||||
}
|
||||
|
||||
|
||||
|
||||
public
|
||||
enum MobilePreferenceError: Swift.Error, Equatable, Hashable, Foundation.LocalizedError {
|
||||
|
||||
@@ -878,6 +1516,31 @@ public func FfiConverterTypeMobileTab_lower(_ value: MobileTab) -> RustBuffer {
|
||||
}
|
||||
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
fileprivate struct FfiConverterSequenceString: FfiConverterRustBuffer {
|
||||
typealias SwiftType = [String]
|
||||
|
||||
public static func write(_ value: [String], into buf: inout [UInt8]) {
|
||||
let len = Int32(value.count)
|
||||
writeInt(&buf, len)
|
||||
for item in value {
|
||||
FfiConverterString.write(item, into: &buf)
|
||||
}
|
||||
}
|
||||
|
||||
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [String] {
|
||||
let len: Int32 = try readInt(&buf)
|
||||
var seq = [String]()
|
||||
seq.reserveCapacity(Int(len))
|
||||
for _ in 0 ..< len {
|
||||
seq.append(try FfiConverterString.read(from: &buf))
|
||||
}
|
||||
return seq
|
||||
}
|
||||
}
|
||||
|
||||
#if swift(>=5.8)
|
||||
@_documentation(visibility: private)
|
||||
#endif
|
||||
@@ -902,6 +1565,17 @@ fileprivate struct FfiConverterSequenceTypeMobilePage: FfiConverterRustBuffer {
|
||||
return seq
|
||||
}
|
||||
}
|
||||
public func mobileOnboardingOperation(serverUrl: String, account: String, repositoryPath: String, applicationToken: String)throws -> MobileOnboardingOperation {
|
||||
return try FfiConverterTypeMobileOnboardingOperation_lift(try rustCallWithError(FfiConverterTypeMobileOnboardingFfiError_lift) {
|
||||
uniffiCallStatus in
|
||||
uniffi_ironstorage_apple_fn_func_mobile_onboarding_operation(
|
||||
FfiConverterString.lower(serverUrl),
|
||||
FfiConverterString.lower(account),
|
||||
FfiConverterString.lower(repositoryPath),
|
||||
FfiConverterString.lower(applicationToken),uniffiCallStatus
|
||||
)
|
||||
})
|
||||
}
|
||||
public func mobileShell() -> MobileShell {
|
||||
return try! FfiConverterTypeMobileShell_lift(try! rustCall() {
|
||||
uniffiCallStatus in
|
||||
@@ -924,6 +1598,14 @@ public func productName() -> String {
|
||||
)
|
||||
})
|
||||
}
|
||||
public func replaceConfiguredMobileApplicationToken(account: String, applicationToken: String)throws {try rustCallWithError(FfiConverterTypeMobileOnboardingFfiError_lift) {
|
||||
uniffiCallStatus in
|
||||
uniffi_ironstorage_apple_fn_func_replace_configured_mobile_application_token(
|
||||
FfiConverterString.lower(account),
|
||||
FfiConverterString.lower(applicationToken),uniffiCallStatus
|
||||
)
|
||||
}
|
||||
}
|
||||
public func setSelectedMobileTab(tab: MobileTab)throws {try rustCallWithError(FfiConverterTypeMobilePreferenceError_lift) {
|
||||
uniffiCallStatus in
|
||||
uniffi_ironstorage_apple_fn_func_set_selected_mobile_tab(
|
||||
@@ -947,6 +1629,9 @@ private let initializationResult: InitializationResult = {
|
||||
if bindings_contract_version != scaffolding_contract_version {
|
||||
return InitializationResult.contractVersionMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_func_mobile_onboarding_operation() != 8354) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_func_mobile_shell() != 42687) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
@@ -956,9 +1641,24 @@ private let initializationResult: InitializationResult = {
|
||||
if (uniffi_ironstorage_apple_checksum_func_product_name() != 43533) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_func_replace_configured_mobile_application_token() != 32386) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_func_set_selected_mobile_tab() != 65280) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileonboardingoperation_cancel() != 49755) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileonboardingoperation_discover() != 2309) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileonboardingoperation_progress() != 28201) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_apple_checksum_method_mobileonboardingoperation_setup() != 3525) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
|
||||
return InitializationResult.ok
|
||||
}()
|
||||
|
||||
@@ -242,6 +242,41 @@ typedef struct UniffiForeignFutureResultVoid {
|
||||
typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureResultVoid
|
||||
);
|
||||
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_CLONE_MOBILEONBOARDINGOPERATION
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_CLONE_MOBILEONBOARDINGOPERATION
|
||||
uint64_t uniffi_ironstorage_apple_fn_clone_mobileonboardingoperation(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FREE_MOBILEONBOARDINGOPERATION
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FREE_MOBILEONBOARDINGOPERATION
|
||||
void uniffi_ironstorage_apple_fn_free_mobileonboardingoperation(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEONBOARDINGOPERATION_CANCEL
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEONBOARDINGOPERATION_CANCEL
|
||||
void uniffi_ironstorage_apple_fn_method_mobileonboardingoperation_cancel(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEONBOARDINGOPERATION_DISCOVER
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEONBOARDINGOPERATION_DISCOVER
|
||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileonboardingoperation_discover(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEONBOARDINGOPERATION_PROGRESS
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEONBOARDINGOPERATION_PROGRESS
|
||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileonboardingoperation_progress(uint64_t ptr, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEONBOARDINGOPERATION_SETUP
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_METHOD_MOBILEONBOARDINGOPERATION_SETUP
|
||||
RustBuffer uniffi_ironstorage_apple_fn_method_mobileonboardingoperation_setup(uint64_t ptr, RustBuffer branch, int8_t use_existing, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_MOBILE_ONBOARDING_OPERATION
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_MOBILE_ONBOARDING_OPERATION
|
||||
uint64_t uniffi_ironstorage_apple_fn_func_mobile_onboarding_operation(RustBuffer server_url, RustBuffer account, RustBuffer repository_path, RustBuffer application_token, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_MOBILE_SHELL
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_MOBILE_SHELL
|
||||
@@ -258,6 +293,11 @@ RustBuffer uniffi_ironstorage_apple_fn_func_mobile_shell_fixture(RustBuffer stat
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_PRODUCT_NAME
|
||||
RustBuffer uniffi_ironstorage_apple_fn_func_product_name(RustCallStatus *_Nonnull out_status
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_REPLACE_CONFIGURED_MOBILE_APPLICATION_TOKEN
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_REPLACE_CONFIGURED_MOBILE_APPLICATION_TOKEN
|
||||
void uniffi_ironstorage_apple_fn_func_replace_configured_mobile_application_token(RustBuffer account, RustBuffer application_token, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_FN_FUNC_SET_SELECTED_MOBILE_TAB
|
||||
@@ -523,6 +563,12 @@ void ffi_ironstorage_apple_rust_future_free_void(uint64_t handle
|
||||
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_APPLE_RUST_FUTURE_COMPLETE_VOID
|
||||
#define UNIFFI_FFIDEF_FFI_IRONSTORAGE_APPLE_RUST_FUTURE_COMPLETE_VOID
|
||||
void ffi_ironstorage_apple_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_MOBILE_ONBOARDING_OPERATION
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_MOBILE_ONBOARDING_OPERATION
|
||||
uint16_t uniffi_ironstorage_apple_checksum_func_mobile_onboarding_operation(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_MOBILE_SHELL
|
||||
@@ -541,12 +587,42 @@ uint16_t uniffi_ironstorage_apple_checksum_func_mobile_shell_fixture(void
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_PRODUCT_NAME
|
||||
uint16_t uniffi_ironstorage_apple_checksum_func_product_name(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_REPLACE_CONFIGURED_MOBILE_APPLICATION_TOKEN
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_REPLACE_CONFIGURED_MOBILE_APPLICATION_TOKEN
|
||||
uint16_t uniffi_ironstorage_apple_checksum_func_replace_configured_mobile_application_token(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_SET_SELECTED_MOBILE_TAB
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_FUNC_SET_SELECTED_MOBILE_TAB
|
||||
uint16_t uniffi_ironstorage_apple_checksum_func_set_selected_mobile_tab(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEONBOARDINGOPERATION_CANCEL
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEONBOARDINGOPERATION_CANCEL
|
||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileonboardingoperation_cancel(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEONBOARDINGOPERATION_DISCOVER
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEONBOARDINGOPERATION_DISCOVER
|
||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileonboardingoperation_discover(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEONBOARDINGOPERATION_PROGRESS
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEONBOARDINGOPERATION_PROGRESS
|
||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileonboardingoperation_progress(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEONBOARDINGOPERATION_SETUP
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_APPLE_CHECKSUM_METHOD_MOBILEONBOARDINGOPERATION_SETUP
|
||||
uint16_t uniffi_ironstorage_apple_checksum_method_mobileonboardingoperation_setup(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_FFI_IRONSTORAGE_APPLE_UNIFFI_CONTRACT_VERSION
|
||||
|
||||
@@ -167,6 +167,23 @@ private final class ShellViewController: UITableViewController {
|
||||
private func apply(_ page: MobilePage) {
|
||||
self.page = page
|
||||
title = page.title
|
||||
if page.state == .empty, shellTab == .home {
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
title: "Set Up",
|
||||
style: .done,
|
||||
target: self,
|
||||
action: #selector(setupRequested)
|
||||
)
|
||||
} else if page.state == .ready, shellTab == .preferences {
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
title: "Update Token",
|
||||
style: .plain,
|
||||
target: self,
|
||||
action: #selector(tokenUpdateRequested)
|
||||
)
|
||||
} else {
|
||||
navigationItem.rightBarButtonItem = nil
|
||||
}
|
||||
refreshControl?.endRefreshing()
|
||||
tableView.reloadData()
|
||||
|
||||
@@ -183,6 +200,14 @@ private final class ShellViewController: UITableViewController {
|
||||
contentUnavailableConfiguration = configuration
|
||||
}
|
||||
|
||||
@objc private func setupRequested() {
|
||||
navigationController?.pushViewController(OnboardingViewController(), animated: true)
|
||||
}
|
||||
|
||||
@objc private func tokenUpdateRequested() {
|
||||
navigationController?.pushViewController(TokenUpdateViewController(), animated: true)
|
||||
}
|
||||
|
||||
private func stateImage(_ state: MobileShellState) -> String {
|
||||
switch state {
|
||||
case .loading: "hourglass"
|
||||
@@ -193,3 +218,482 @@ private final class ShellViewController: UITableViewController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class TokenUpdateViewController: UITableViewController {
|
||||
private let accountField = UITextField()
|
||||
private let tokenField = UITextField()
|
||||
private var updateTask: Task<Void, Never>?
|
||||
private var generation = 0
|
||||
private var isWorking = false
|
||||
|
||||
init() {
|
||||
super.init(style: .insetGrouped)
|
||||
title = "Update Token"
|
||||
navigationItem.largeTitleDisplayMode = .never
|
||||
for field in [accountField, tokenField] {
|
||||
field.borderStyle = .none
|
||||
field.clearButtonMode = .whileEditing
|
||||
field.autocorrectionType = .no
|
||||
field.autocapitalizationType = .none
|
||||
field.adjustsFontForContentSizeCategory = true
|
||||
field.font = .preferredFont(forTextStyle: .body)
|
||||
}
|
||||
accountField.placeholder = "Account name"
|
||||
tokenField.placeholder = "New application token"
|
||||
tokenField.textContentType = .oneTimeCode
|
||||
tokenField.isSecureTextEntry = true
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) is not supported")
|
||||
}
|
||||
|
||||
deinit {
|
||||
updateTask?.cancel()
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
generation += 1
|
||||
updateTask?.cancel()
|
||||
}
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int { 2 }
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
section == 0 ? 2 : 1
|
||||
}
|
||||
|
||||
override func tableView(
|
||||
_ tableView: UITableView,
|
||||
titleForHeaderInSection section: Int
|
||||
) -> String? {
|
||||
section == 0 ? "HTTPS Credential" : nil
|
||||
}
|
||||
|
||||
override func tableView(
|
||||
_ tableView: UITableView,
|
||||
titleForFooterInSection section: Int
|
||||
) -> String? {
|
||||
section == 0
|
||||
? "The existing token is never displayed. Updating replaces it only in protected system storage."
|
||||
: nil
|
||||
}
|
||||
|
||||
override func tableView(
|
||||
_ tableView: UITableView,
|
||||
cellForRowAt indexPath: IndexPath
|
||||
) -> UITableViewCell {
|
||||
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
|
||||
if indexPath.section == 1 {
|
||||
cell.textLabel?.text = isWorking ? "Updating…" : "Replace Token"
|
||||
cell.textLabel?.textColor = isWorking ? .secondaryLabel : view.tintColor
|
||||
cell.textLabel?.textAlignment = .center
|
||||
cell.isUserInteractionEnabled = !isWorking
|
||||
return cell
|
||||
}
|
||||
let field = indexPath.row == 0 ? accountField : tokenField
|
||||
field.translatesAutoresizingMaskIntoConstraints = false
|
||||
field.isEnabled = !isWorking
|
||||
cell.contentView.addSubview(field)
|
||||
NSLayoutConstraint.activate([
|
||||
field.leadingAnchor.constraint(equalTo: cell.contentView.layoutMarginsGuide.leadingAnchor),
|
||||
field.trailingAnchor.constraint(equalTo: cell.contentView.layoutMarginsGuide.trailingAnchor),
|
||||
field.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 10),
|
||||
field.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -10)
|
||||
])
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
guard indexPath.section == 1, !isWorking else { return }
|
||||
replaceToken()
|
||||
}
|
||||
|
||||
private func replaceToken() {
|
||||
generation += 1
|
||||
let current = generation
|
||||
isWorking = true
|
||||
navigationItem.prompt = "Saving in protected system storage."
|
||||
tableView.reloadData()
|
||||
let account = accountField.text ?? ""
|
||||
let token = tokenField.text ?? ""
|
||||
updateTask = Task { [weak self] in
|
||||
let result = await Task.detached(priority: .userInitiated) {
|
||||
do {
|
||||
try replaceConfiguredMobileApplicationToken(
|
||||
account: account,
|
||||
applicationToken: token
|
||||
)
|
||||
return Result<Void, OnboardingFailure>.success(())
|
||||
} catch let error as MobileOnboardingFfiError {
|
||||
return .failure(OnboardingFailure(error))
|
||||
} catch {
|
||||
return .failure(.unexpected)
|
||||
}
|
||||
}.value
|
||||
guard let self, current == generation else { return }
|
||||
isWorking = false
|
||||
navigationItem.prompt = nil
|
||||
tableView.reloadData()
|
||||
switch result {
|
||||
case .success:
|
||||
tokenField.text = nil
|
||||
let alert = UIAlertController(
|
||||
title: "Token Updated",
|
||||
message: "The new application token is ready for HTTPS Git operations.",
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "Done", style: .default) { [weak self] _ in
|
||||
self?.navigationController?.popViewController(animated: true)
|
||||
})
|
||||
present(alert, animated: true)
|
||||
case let .failure(failure):
|
||||
let alert = UIAlertController(
|
||||
title: failure.title,
|
||||
message: failure.detail,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .cancel))
|
||||
present(alert, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class OnboardingViewController: UITableViewController {
|
||||
private let serverField = UITextField()
|
||||
private let accountField = UITextField()
|
||||
private let repositoryField = UITextField()
|
||||
private let tokenField = UITextField()
|
||||
private var branches: [String] = []
|
||||
private var selectedBranch = 0
|
||||
private var operation: MobileOnboardingOperation?
|
||||
private var workTask: Task<Void, Never>?
|
||||
private var progressTask: Task<Void, Never>?
|
||||
private var generation = 0
|
||||
private var isWorking = false
|
||||
|
||||
init() {
|
||||
super.init(style: .insetGrouped)
|
||||
title = "Connect Store"
|
||||
navigationItem.largeTitleDisplayMode = .never
|
||||
configureFields()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) is not supported")
|
||||
}
|
||||
|
||||
deinit {
|
||||
operation?.cancel()
|
||||
workTask?.cancel()
|
||||
progressTask?.cancel()
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
cancelWork()
|
||||
}
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
branches.isEmpty ? 2 : 3
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
switch section {
|
||||
case 0: 4
|
||||
case 1: 1
|
||||
default: 2
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(
|
||||
_ tableView: UITableView,
|
||||
titleForHeaderInSection section: Int
|
||||
) -> String? {
|
||||
switch section {
|
||||
case 0: "HTTPS Repository"
|
||||
case 1: branches.isEmpty ? nil : "Remote"
|
||||
default: "Local Clone"
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(
|
||||
_ tableView: UITableView,
|
||||
titleForFooterInSection section: Int
|
||||
) -> String? {
|
||||
section == 0 ? "The application token is stored in protected system storage, never in configuration." : nil
|
||||
}
|
||||
|
||||
override func tableView(
|
||||
_ tableView: UITableView,
|
||||
cellForRowAt indexPath: IndexPath
|
||||
) -> UITableViewCell {
|
||||
if indexPath.section == 0 {
|
||||
return fieldCell(indexPath.row)
|
||||
}
|
||||
let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
|
||||
if branches.isEmpty {
|
||||
cell.textLabel?.text = "Discover Branches"
|
||||
cell.textLabel?.textColor = view.tintColor
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
} else if indexPath.section == 1 {
|
||||
cell.textLabel?.text = "Branch"
|
||||
cell.detailTextLabel?.text = branches[selectedBranch]
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
} else if indexPath.row == 0 {
|
||||
cell.textLabel?.text = "Clone Password Store"
|
||||
cell.textLabel?.textColor = view.tintColor
|
||||
} else {
|
||||
cell.textLabel?.text = "Use Existing Clone"
|
||||
cell.textLabel?.textColor = .secondaryLabel
|
||||
}
|
||||
cell.isUserInteractionEnabled = !isWorking
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
guard !isWorking else { return }
|
||||
if branches.isEmpty, indexPath.section == 1 {
|
||||
discoverBranches()
|
||||
} else if indexPath.section == 1 {
|
||||
chooseBranch(from: tableView.cellForRow(at: indexPath))
|
||||
} else if indexPath.section == 2 {
|
||||
runSetup(useExisting: indexPath.row == 1)
|
||||
}
|
||||
}
|
||||
|
||||
private func configureFields() {
|
||||
for field in [serverField, accountField, repositoryField, tokenField] {
|
||||
field.borderStyle = .none
|
||||
field.clearButtonMode = .whileEditing
|
||||
field.autocorrectionType = .no
|
||||
field.returnKeyType = .next
|
||||
field.adjustsFontForContentSizeCategory = true
|
||||
field.font = .preferredFont(forTextStyle: .body)
|
||||
}
|
||||
serverField.placeholder = "https://git.example.com"
|
||||
serverField.textContentType = .URL
|
||||
serverField.keyboardType = .URL
|
||||
serverField.autocapitalizationType = .none
|
||||
accountField.placeholder = "Account name"
|
||||
accountField.autocapitalizationType = .none
|
||||
repositoryField.placeholder = "owner/password-store"
|
||||
repositoryField.autocapitalizationType = .none
|
||||
tokenField.placeholder = "Application token"
|
||||
tokenField.textContentType = .oneTimeCode
|
||||
tokenField.isSecureTextEntry = true
|
||||
}
|
||||
|
||||
private func fieldCell(_ row: Int) -> UITableViewCell {
|
||||
let field = [serverField, accountField, repositoryField, tokenField][row]
|
||||
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
|
||||
field.translatesAutoresizingMaskIntoConstraints = false
|
||||
cell.contentView.addSubview(field)
|
||||
NSLayoutConstraint.activate([
|
||||
field.leadingAnchor.constraint(equalTo: cell.contentView.layoutMarginsGuide.leadingAnchor),
|
||||
field.trailingAnchor.constraint(equalTo: cell.contentView.layoutMarginsGuide.trailingAnchor),
|
||||
field.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 10),
|
||||
field.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -10)
|
||||
])
|
||||
return cell
|
||||
}
|
||||
|
||||
private func makeOperation() throws -> MobileOnboardingOperation {
|
||||
try mobileOnboardingOperation(
|
||||
serverUrl: serverField.text ?? "",
|
||||
account: accountField.text ?? "",
|
||||
repositoryPath: repositoryField.text ?? "",
|
||||
applicationToken: tokenField.text ?? ""
|
||||
)
|
||||
}
|
||||
|
||||
private func discoverBranches() {
|
||||
do {
|
||||
let operation = try makeOperation()
|
||||
begin(operation, title: "Checking Repository") { operation in
|
||||
let discovery = try operation.discover()
|
||||
return .discovered(discovery)
|
||||
}
|
||||
} catch {
|
||||
present(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func runSetup(useExisting: Bool) {
|
||||
guard branches.indices.contains(selectedBranch) else { return }
|
||||
do {
|
||||
let operation = try makeOperation()
|
||||
let branch = branches[selectedBranch]
|
||||
begin(operation, title: useExisting ? "Opening Store" : "Cloning Store") { operation in
|
||||
let outcome = try operation.setup(branch: branch, useExisting: useExisting)
|
||||
return .completed(outcome)
|
||||
}
|
||||
} catch {
|
||||
present(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func begin(
|
||||
_ operation: MobileOnboardingOperation,
|
||||
title: String,
|
||||
work: @escaping @Sendable (MobileOnboardingOperation) throws -> WorkResult
|
||||
) {
|
||||
cancelWork()
|
||||
generation += 1
|
||||
let current = generation
|
||||
self.operation = operation
|
||||
isWorking = true
|
||||
showProgress(title: title, detail: "Preparing secure storage.")
|
||||
tableView.reloadData()
|
||||
progressTask = Task { [weak self] in
|
||||
while !Task.isCancelled {
|
||||
do {
|
||||
try await Task.sleep(for: .milliseconds(150))
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
guard !Task.isCancelled, let self, current == generation else { return }
|
||||
let progress = operation.progress()
|
||||
showProgress(title: progress.title, detail: progress.detail)
|
||||
}
|
||||
}
|
||||
workTask = Task { [weak self] in
|
||||
let result = await Task.detached(priority: .userInitiated) {
|
||||
do {
|
||||
return Result<WorkResult, OnboardingFailure>.success(try work(operation))
|
||||
} catch let error as MobileOnboardingFfiError {
|
||||
return .failure(OnboardingFailure(error))
|
||||
} catch {
|
||||
return .failure(.unexpected)
|
||||
}
|
||||
}.value
|
||||
guard let self, current == generation else { return }
|
||||
finish(result)
|
||||
}
|
||||
}
|
||||
|
||||
private func finish(_ result: Result<WorkResult, OnboardingFailure>) {
|
||||
progressTask?.cancel()
|
||||
progressTask = nil
|
||||
operation = nil
|
||||
isWorking = false
|
||||
navigationItem.titleView = nil
|
||||
navigationItem.prompt = nil
|
||||
switch result {
|
||||
case let .success(.discovered(discovery)):
|
||||
branches = discovery.branches
|
||||
selectedBranch = min(Int(discovery.selectedBranch), max(branches.count - 1, 0))
|
||||
tableView.reloadData()
|
||||
case let .success(.completed(outcome)):
|
||||
tokenField.text = nil
|
||||
let alert = UIAlertController(title: outcome.title, message: outcome.detail, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "Done", style: .default) { [weak self] _ in
|
||||
self?.navigationController?.popViewController(animated: true)
|
||||
})
|
||||
present(alert, animated: true)
|
||||
case let .failure(failure):
|
||||
tableView.reloadData()
|
||||
present(failure)
|
||||
}
|
||||
}
|
||||
|
||||
private func chooseBranch(from source: UIView?) {
|
||||
let sheet = UIAlertController(title: "Branch", message: nil, preferredStyle: .actionSheet)
|
||||
for (index, branch) in branches.enumerated() {
|
||||
sheet.addAction(UIAlertAction(title: branch, style: .default) { [weak self] _ in
|
||||
self?.selectedBranch = index
|
||||
self?.tableView.reloadSections(IndexSet(integer: 1), with: .automatic)
|
||||
})
|
||||
}
|
||||
sheet.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||
sheet.popoverPresentationController?.sourceView = source
|
||||
sheet.popoverPresentationController?.sourceRect = source?.bounds ?? .zero
|
||||
present(sheet, animated: true)
|
||||
}
|
||||
|
||||
private func present(_ error: Error) {
|
||||
if let error = error as? MobileOnboardingFfiError {
|
||||
present(OnboardingFailure(error))
|
||||
} else {
|
||||
present(.unexpected)
|
||||
}
|
||||
}
|
||||
|
||||
private func present(_ failure: OnboardingFailure) {
|
||||
let alert = UIAlertController(title: failure.title, message: failure.detail, preferredStyle: .alert)
|
||||
if failure.kind == .existingClone, !branches.isEmpty {
|
||||
alert.addAction(UIAlertAction(title: "Use Existing", style: .default) { [weak self] _ in
|
||||
self?.runSetup(useExisting: true)
|
||||
})
|
||||
}
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .cancel))
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
private func showProgress(title: String, detail: String) {
|
||||
let spinner = UIActivityIndicatorView(style: .medium)
|
||||
spinner.startAnimating()
|
||||
spinner.accessibilityLabel = "In progress"
|
||||
let label = UILabel()
|
||||
label.text = title
|
||||
label.font = .preferredFont(forTextStyle: .headline)
|
||||
label.adjustsFontForContentSizeCategory = true
|
||||
let stack = UIStackView(arrangedSubviews: [spinner, label])
|
||||
stack.spacing = 8
|
||||
navigationItem.titleView = stack
|
||||
navigationItem.prompt = detail
|
||||
}
|
||||
|
||||
private func cancelWork() {
|
||||
generation += 1
|
||||
operation?.cancel()
|
||||
operation = nil
|
||||
workTask?.cancel()
|
||||
workTask = nil
|
||||
progressTask?.cancel()
|
||||
progressTask = nil
|
||||
navigationItem.prompt = nil
|
||||
navigationItem.titleView = nil
|
||||
isWorking = false
|
||||
}
|
||||
}
|
||||
|
||||
private enum WorkResult: Sendable {
|
||||
case discovered(MobileOnboardingDiscovery)
|
||||
case completed(MobileOnboardingOutcome)
|
||||
}
|
||||
|
||||
private struct OnboardingFailure: Error, Sendable {
|
||||
let kind: MobileOnboardingErrorKind?
|
||||
let title: String
|
||||
let detail: String
|
||||
|
||||
init(_ error: MobileOnboardingFfiError) {
|
||||
switch error {
|
||||
case let .Failed(kind, title, detail):
|
||||
self.kind = kind
|
||||
self.title = title
|
||||
self.detail = detail
|
||||
}
|
||||
}
|
||||
|
||||
static let unexpected = OnboardingFailure(
|
||||
kind: nil,
|
||||
title: "Setup Failed",
|
||||
detail: "IronStorage could not complete setup."
|
||||
)
|
||||
|
||||
private init(kind: MobileOnboardingErrorKind?, title: String, detail: String) {
|
||||
self.kind = kind
|
||||
self.title = title
|
||||
self.detail = detail
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user