// // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import SignalServiceKit public enum RegistrationPhoneNumberViewState: Equatable { case registration(RegistrationMode) case changingNumber(ChangingNumberMode) public enum RegistrationMode: Equatable { case initialRegistration(InitialRegistration) case reregistration(Reregistration) } public enum ChangingNumberMode: Equatable { case initialEntry(ChangeNumberInitialEntry) case confirmation(ChangeNumberConfirmation) } public struct InitialRegistration: Equatable { /// previouslyEnteredE164 is if the user entered a number, quit, and came back. /// Will be used to pre-populate the entry field. let previouslyEnteredE164: E164? let validationError: ValidationError? let canExitRegistration: Bool } public struct Reregistration: Equatable { let e164: E164 let validationError: ValidationError? let canExitRegistration: Bool } public struct ChangeNumberInitialEntry: Equatable { let oldE164: E164 let newE164: E164? let hasConfirmed: Bool let invalidNumberError: ValidationError.InvalidNumber? } public struct ChangeNumberConfirmation: Equatable { let oldE164: E164 let newE164: E164 let rateLimitedError: ValidationError.RateLimited? } public enum ValidationError: Equatable { case invalidNumber(InvalidNumber) case rateLimited(RateLimited) public struct InvalidNumber: Equatable { let invalidE164: E164 } public struct RateLimited: Equatable { let expiration: Date let e164: E164 } } } extension RegistrationPhoneNumberViewState.ValidationError { func canSubmit(e164: E164, dateProvider: DateProvider) -> Bool { switch self { case let .invalidNumber(error): return error.canSubmit(e164: e164) case let .rateLimited(error): return error.canSubmit(e164: e164, dateProvider: dateProvider) } } func warningLabelText(e164: E164, dateProvider: DateProvider) -> String? { switch self { case .invalidNumber(let error): return error.warningLabelText(e164: e164) case let .rateLimited(error): return error.warningLabelText(e164: e164, dateProvider: dateProvider) } } } extension RegistrationPhoneNumberViewState.ValidationError.InvalidNumber { func canSubmit(e164: E164) -> Bool { return e164 != invalidE164 } func warningLabelText(e164: E164) -> String? { guard self.invalidE164 == e164 else { return nil } return OWSLocalizedString( "ONBOARDING_PHONE_NUMBER_VALIDATION_WARNING", comment: "Label indicating that the phone number is invalid in the 'onboarding phone number' view." ) } } extension RegistrationPhoneNumberViewState.ValidationError.RateLimited { func canSubmit(e164: E164, dateProvider: DateProvider) -> Bool { return dateProvider() >= expiration || e164 != self.e164 } func warningLabelText(e164: E164, dateProvider: DateProvider) -> String? { guard e164 == self.e164 else { return nil } let now = dateProvider() if now >= expiration { return nil } let rateLimitFormat = OWSLocalizedString( "ONBOARDING_PHONE_NUMBER_RATE_LIMIT_WARNING_FORMAT", comment: "Label indicating that registration has been ratelimited. Embeds {{remaining time string}}." ) let retryAfterFormatter: DateFormatter = { let result = DateFormatter() result.dateFormat = "m:ss" result.timeZone = TimeZone(identifier: "UTC")! return result }() let timeRemaining = max(expiration.timeIntervalSince(now), 0) let durationString = retryAfterFormatter.string(from: Date(timeIntervalSinceReferenceDate: timeRemaining)) return String(format: rateLimitFormat, durationString) } }