Consolidate onboarding into single login page with native styling and full localization
Merge the two-page welcome/login flow into a single page with the app icon, title, subtitle, and login inputs all on one screen. Replace the custom blue background and white-on-blue styling with native iOS system colors and button styles. Add missing translations (de, es, fr) for all onboarding strings and fix localization by using LocalizedStringKey and String(localized:). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,41 +10,59 @@ import OSLog
|
||||
import SwiftUI
|
||||
|
||||
struct OnboardingView: View {
|
||||
@State var selectedTab: Int = 0
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $selectedTab) {
|
||||
WelcomeTab().tag(0)
|
||||
LoginTab().tag(1)
|
||||
}
|
||||
.tabViewStyle(.page)
|
||||
.background(
|
||||
selectedTab == 1 ? Color.nextcloudBlue.ignoresSafeArea() : Color(uiColor: .systemBackground).ignoresSafeArea()
|
||||
)
|
||||
.animation(.easeInOut, value: selectedTab)
|
||||
}
|
||||
}
|
||||
@State var loginMethod: LoginMethod = .v2
|
||||
|
||||
// Login error alert
|
||||
@State var showAlert: Bool = false
|
||||
@State var alertMessage: String = String(localized: "Error: Could not connect to server.")
|
||||
|
||||
struct WelcomeTab: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .center) {
|
||||
Spacer()
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(spacing: 0) {
|
||||
Image("cookbook-icon")
|
||||
.resizable()
|
||||
.frame(width: 120, height: 120)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
Text("Thank you for downloading")
|
||||
.font(.headline)
|
||||
.frame(width: 80, height: 80)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 18))
|
||||
.padding(.top, 48)
|
||||
Text("Cookbook Client")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
Spacer()
|
||||
Text("This application is an open source effort. If you're interested in suggesting or contributing new features, or you encounter any problems, please use the support link or visit the GitHub repository in the app settings.")
|
||||
.padding()
|
||||
Spacer()
|
||||
.padding(.top, 10)
|
||||
Text("Thanks for downloading! Sign in to your Nextcloud server to get started.")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, 32)
|
||||
.padding(.top, 6)
|
||||
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Picker("Login Method", selection: $loginMethod) {
|
||||
Text("Nextcloud Login").tag(LoginMethod.v2)
|
||||
Text("App Token Login").tag(LoginMethod.token)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
|
||||
if loginMethod == .token {
|
||||
TokenLoginView(
|
||||
showAlert: $showAlert,
|
||||
alertMessage: $alertMessage
|
||||
)
|
||||
} else if loginMethod == .v2 {
|
||||
V2LoginView(
|
||||
showAlert: $showAlert,
|
||||
alertMessage: $alertMessage
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(.top, 28)
|
||||
}
|
||||
.padding()
|
||||
.fontDesign(.rounded)
|
||||
.padding()
|
||||
.alert(alertMessage, isPresented: $showAlert) {
|
||||
Button("Ok", role: .cancel) { }
|
||||
}
|
||||
}
|
||||
.background(Color(uiColor: .systemGroupedBackground).ignoresSafeArea())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +77,7 @@ enum LoginMethod {
|
||||
|
||||
enum TokenLoginStage: LoginStage {
|
||||
case serverAddress, userName, appToken, validate
|
||||
|
||||
|
||||
func next() -> TokenLoginStage {
|
||||
switch self {
|
||||
case .serverAddress: return .userName
|
||||
@@ -68,7 +86,7 @@ enum TokenLoginStage: LoginStage {
|
||||
case .validate: return .validate
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func previous() -> TokenLoginStage {
|
||||
switch self {
|
||||
case .serverAddress: return .serverAddress
|
||||
@@ -79,160 +97,88 @@ enum TokenLoginStage: LoginStage {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
struct LoginTab: View {
|
||||
@State var loginMethod: LoginMethod = .v2
|
||||
|
||||
// Login error alert
|
||||
@State var showAlert: Bool = false
|
||||
@State var alertMessage: String = "Error: Could not connect to server."
|
||||
|
||||
var body: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(alignment: .leading) {
|
||||
Spacer()
|
||||
Picker("Login Method", selection: $loginMethod) {
|
||||
Text("Nextcloud Login").tag(LoginMethod.v2)
|
||||
Text("App Token Login").tag(LoginMethod.token)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.foregroundColor(.white)
|
||||
.padding()
|
||||
if loginMethod == .token {
|
||||
TokenLoginView(
|
||||
showAlert: $showAlert,
|
||||
alertMessage: $alertMessage
|
||||
)
|
||||
}
|
||||
else if loginMethod == .v2 {
|
||||
V2LoginView(
|
||||
showAlert: $showAlert,
|
||||
alertMessage: $alertMessage
|
||||
)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
||||
.fontDesign(.rounded)
|
||||
.padding()
|
||||
.alert(alertMessage, isPresented: $showAlert) {
|
||||
Button("Ok", role: .cancel) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
struct LoginLabel: View {
|
||||
let text: String
|
||||
let text: LocalizedStringKey
|
||||
var body: some View {
|
||||
Text(text)
|
||||
.foregroundColor(.white)
|
||||
.font(.headline)
|
||||
.padding(.vertical, 5)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
struct BorderedLoginTextField: View {
|
||||
var example: String
|
||||
var example: LocalizedStringKey
|
||||
@Binding var text: String
|
||||
@State var color: Color = .white
|
||||
|
||||
|
||||
var body: some View {
|
||||
TextField(example, text: $text)
|
||||
.textFieldStyle(.plain)
|
||||
.autocorrectionDisabled()
|
||||
.textInputAutocapitalization(.never)
|
||||
.foregroundColor(color)
|
||||
.tint(color)
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(.white, lineWidth: 2)
|
||||
.foregroundColor(.clear)
|
||||
)
|
||||
|
||||
.background(Color(uiColor: .secondarySystemGroupedBackground))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
}
|
||||
}
|
||||
|
||||
struct LoginTextField: View {
|
||||
var example: String
|
||||
var example: LocalizedStringKey
|
||||
@Binding var text: String
|
||||
@State var color: Color = .white
|
||||
|
||||
|
||||
var body: some View {
|
||||
TextField(example, text: $text)
|
||||
.textFieldStyle(.plain)
|
||||
.autocorrectionDisabled()
|
||||
.textInputAutocapitalization(.never)
|
||||
.foregroundColor(color)
|
||||
.tint(color)
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.foregroundColor(Color.white.opacity(0.2))
|
||||
)
|
||||
.background(Color(uiColor: .secondarySystemGroupedBackground))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ServerAddressField: View {
|
||||
@ObservedObject var userSettings = UserSettings.shared
|
||||
@State var serverProtocol: ServerProtocol = UserSettings.shared.serverProtocol == ServerProtocol.http.rawValue ? ServerProtocol.http : ServerProtocol.https
|
||||
|
||||
|
||||
enum ServerProtocol: String {
|
||||
case https="https://", http="http://"
|
||||
|
||||
|
||||
static let all = [https, http]
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
LoginLabel(text: "Server address")
|
||||
VStack(alignment: .leading) {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
HStack {
|
||||
Picker(ServerProtocol.https.rawValue, selection: $serverProtocol) {
|
||||
ForEach(ServerProtocol.all, id: \.self) {
|
||||
Text($0.rawValue)
|
||||
}
|
||||
}.pickerStyle(.menu)
|
||||
.tint(.white)
|
||||
.font(.headline)
|
||||
}
|
||||
.pickerStyle(.menu)
|
||||
.tint(.accentColor)
|
||||
.onChange(of: serverProtocol) { value in
|
||||
Logger.view.debug("\(value.rawValue)")
|
||||
userSettings.serverProtocol = value.rawValue
|
||||
}
|
||||
|
||||
|
||||
TextField("e.g.: example.com", text: $userSettings.serverAddress)
|
||||
.textFieldStyle(.plain)
|
||||
.autocorrectionDisabled()
|
||||
.textInputAutocapitalization(.never)
|
||||
.foregroundStyle(.white)
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.foregroundColor(Color.white.opacity(0.2))
|
||||
)
|
||||
|
||||
.padding(10)
|
||||
.background(Color(uiColor: .secondarySystemGroupedBackground))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
}
|
||||
|
||||
LoginLabel(text: "Full server address")
|
||||
.padding(.top)
|
||||
|
||||
Text(userSettings.serverProtocol + userSettings.serverAddress)
|
||||
.foregroundColor(.white)
|
||||
.padding(.vertical, 5)
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(.white, lineWidth: 2)
|
||||
.foregroundColor(.clear)
|
||||
)
|
||||
.background(Color(uiColor: .secondarySystemGroupedBackground))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,6 +188,5 @@ struct ServerAddressField_Preview: PreviewProvider {
|
||||
ServerAddressField()
|
||||
.previewLayout(.sizeThatFits)
|
||||
.padding()
|
||||
.background(Color.nextcloudBlue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,56 +15,55 @@ struct TokenLoginView: View {
|
||||
@Binding var showAlert: Bool
|
||||
@Binding var alertMessage: String
|
||||
@FocusState private var focusedField: Field?
|
||||
|
||||
|
||||
@State var userSettings = UserSettings.shared
|
||||
|
||||
|
||||
// TextField handling
|
||||
enum Field {
|
||||
case server
|
||||
case username
|
||||
case token
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
ServerAddressField()
|
||||
.padding(.bottom)
|
||||
|
||||
LoginLabel(text: "User name")
|
||||
BorderedLoginTextField(example: "username", text: $userSettings.username)
|
||||
.focused($focusedField, equals: .username)
|
||||
.textContentType(.username)
|
||||
.submitLabel(.next)
|
||||
.padding(.bottom)
|
||||
|
||||
|
||||
LoginLabel(text: "App Token")
|
||||
BorderedLoginTextField(example: "can be generated in security settings of your nextcloud", text: $userSettings.token)
|
||||
.focused($focusedField, equals: .token)
|
||||
.textContentType(.password)
|
||||
.submitLabel(.join)
|
||||
HStack{
|
||||
Spacer()
|
||||
Button {
|
||||
Task {
|
||||
if await loginCheck(nextcloudLogin: false) {
|
||||
userSettings.onboarding = false
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text("Submit")
|
||||
.foregroundColor(.white)
|
||||
.font(.headline)
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(Color.white, lineWidth: 2)
|
||||
.foregroundColor(.clear)
|
||||
)
|
||||
}
|
||||
.padding()
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
LoginLabel(text: "User name")
|
||||
BorderedLoginTextField(example: "username", text: $userSettings.username)
|
||||
.focused($focusedField, equals: .username)
|
||||
.textContentType(.username)
|
||||
.submitLabel(.next)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
LoginLabel(text: "App Token")
|
||||
BorderedLoginTextField(example: "can be generated in security settings of your nextcloud", text: $userSettings.token)
|
||||
.focused($focusedField, equals: .token)
|
||||
.textContentType(.password)
|
||||
.submitLabel(.join)
|
||||
}
|
||||
|
||||
Button {
|
||||
Task {
|
||||
if await loginCheck(nextcloudLogin: false) {
|
||||
userSettings.onboarding = false
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Label("Submit", systemImage: "person.badge.key")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 10)
|
||||
.foregroundStyle(Color.nextcloudBlue)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(Color.nextcloudBlue.opacity(0.1))
|
||||
)
|
||||
}
|
||||
.padding(.top, 4)
|
||||
}
|
||||
.onSubmit {
|
||||
switch focusedField {
|
||||
@@ -77,14 +76,14 @@ struct TokenLoginView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func loginCheck(nextcloudLogin: Bool) async -> Bool {
|
||||
if userSettings.serverAddress == "" {
|
||||
alertMessage = "Please enter a server address!"
|
||||
alertMessage = String(localized: "Please enter a server address!")
|
||||
showAlert = true
|
||||
return false
|
||||
} else if !nextcloudLogin && (userSettings.username == "" || userSettings.token == "") {
|
||||
alertMessage = "Please enter a user name and app token!"
|
||||
alertMessage = String(localized: "Please enter a user name and app token!")
|
||||
showAlert = true
|
||||
return false
|
||||
}
|
||||
@@ -95,7 +94,7 @@ struct TokenLoginView: View {
|
||||
let _ = try await client.getCategories()
|
||||
return true
|
||||
} catch {
|
||||
alertMessage = "Login failed. Please check your inputs and internet connection."
|
||||
alertMessage = String(localized: "Login failed. Please check your inputs and internet connection.")
|
||||
showAlert = true
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -12,14 +12,14 @@ import WebKit
|
||||
|
||||
enum V2LoginStage: LoginStage {
|
||||
case login, validate
|
||||
|
||||
|
||||
func next() -> V2LoginStage {
|
||||
switch self {
|
||||
case .login: return .validate
|
||||
case .validate: return .validate
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func previous() -> V2LoginStage {
|
||||
switch self {
|
||||
case .login: return .login
|
||||
@@ -33,105 +33,101 @@ enum V2LoginStage: LoginStage {
|
||||
struct V2LoginView: View {
|
||||
@Binding var showAlert: Bool
|
||||
@Binding var alertMessage: String
|
||||
|
||||
|
||||
@State var loginStage: V2LoginStage = .login
|
||||
@State var loginRequest: LoginV2Request? = nil
|
||||
@State var presentBrowser = false
|
||||
|
||||
|
||||
// TextField handling
|
||||
enum Field {
|
||||
case server
|
||||
case username
|
||||
case token
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
ServerAddressField()
|
||||
CollapsibleView {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Make sure to enter the server address in the form 'example.com', or \n'<server address>:<port>'\n when a non-standard port is used.")
|
||||
.padding(.bottom)
|
||||
Text("The 'Login' button will open a web browser. Please follow the login instructions provided there.\nAfter a successful login, return to this application and press 'Validate'.")
|
||||
.padding(.bottom)
|
||||
Text("If the login button does not open your browser, use the 'Copy Link' button and paste the link in your browser manually.")
|
||||
}
|
||||
} title: {
|
||||
Text("Show help")
|
||||
.foregroundColor(.white)
|
||||
.font(.headline)
|
||||
}.padding()
|
||||
|
||||
if loginRequest != nil {
|
||||
Button("Copy Link") {
|
||||
UIPasteboard.general.string = loginRequest!.login
|
||||
}
|
||||
.font(.headline)
|
||||
.foregroundStyle(.white)
|
||||
.padding()
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
ServerAddressField()
|
||||
|
||||
CollapsibleView(titleColor: .secondary) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Make sure to enter the server address in the form 'example.com', or '<server address>:<port>' when a non-standard port is used.")
|
||||
Text("The 'Login' button will open a web browser. Please follow the login instructions provided there. After a successful login, return to this application and press 'Validate'.")
|
||||
Text("If the login button does not open your browser, use the 'Copy Link' button and paste the link in your browser manually.")
|
||||
}
|
||||
|
||||
HStack {
|
||||
Button {
|
||||
if UserSettings.shared.serverAddress == "" {
|
||||
alertMessage = "Please enter a valid server address."
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
let error = await sendLoginV2Request()
|
||||
if let error = error {
|
||||
alertMessage = "A network error occured (\(error.localizedDescription))."
|
||||
showAlert = true
|
||||
}
|
||||
if let loginRequest = loginRequest {
|
||||
presentBrowser = true
|
||||
//await UIApplication.shared.open(URL(string: loginRequest.login)!)
|
||||
} else {
|
||||
alertMessage = "Unable to reach server. Please check your server address and internet connection."
|
||||
showAlert = true
|
||||
}
|
||||
}
|
||||
loginStage = loginStage.next()
|
||||
} label: {
|
||||
Text("Login")
|
||||
.foregroundColor(.white)
|
||||
.font(.headline)
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(Color.white, lineWidth: 2)
|
||||
.foregroundColor(.clear)
|
||||
)
|
||||
}.padding()
|
||||
|
||||
if loginStage == .validate {
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
// fetch login v2 response
|
||||
Task {
|
||||
let (response, error) = await fetchLoginV2Response()
|
||||
checkLogin(response: response, error: error)
|
||||
}
|
||||
} label: {
|
||||
Text("Validate")
|
||||
.foregroundColor(.white)
|
||||
.font(.headline)
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(Color.white, lineWidth: 2)
|
||||
.foregroundColor(.clear)
|
||||
)
|
||||
}
|
||||
.disabled(loginRequest == nil ? true : false)
|
||||
.padding()
|
||||
}
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
} title: {
|
||||
Text("Show help")
|
||||
.font(.subheadline)
|
||||
}
|
||||
|
||||
if loginRequest != nil {
|
||||
Button {
|
||||
UIPasteboard.general.string = loginRequest!.login
|
||||
} label: {
|
||||
Label("Copy Link", systemImage: "doc.on.doc")
|
||||
.font(.subheadline)
|
||||
}
|
||||
}
|
||||
|
||||
HStack(spacing: 12) {
|
||||
Button {
|
||||
if UserSettings.shared.serverAddress == "" {
|
||||
alertMessage = String(localized: "Please enter a valid server address.")
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
let error = await sendLoginV2Request()
|
||||
if let error = error {
|
||||
alertMessage = String(localized: "A network error occurred. Please try again.")
|
||||
showAlert = true
|
||||
}
|
||||
if let _ = loginRequest {
|
||||
presentBrowser = true
|
||||
} else {
|
||||
alertMessage = String(localized: "Unable to reach server. Please check your server address and internet connection.")
|
||||
showAlert = true
|
||||
}
|
||||
}
|
||||
loginStage = loginStage.next()
|
||||
} label: {
|
||||
Label("Login", systemImage: "person.badge.key")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 10)
|
||||
.foregroundStyle(Color.nextcloudBlue)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(Color.nextcloudBlue.opacity(0.1))
|
||||
)
|
||||
}
|
||||
|
||||
if loginStage == .validate {
|
||||
Button {
|
||||
Task {
|
||||
let (response, error) = await fetchLoginV2Response()
|
||||
checkLogin(response: response, error: error)
|
||||
}
|
||||
} label: {
|
||||
Label("Validate", systemImage: "checkmark.circle.fill")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 10)
|
||||
.foregroundStyle(Color.nextcloudBlue)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(Color.nextcloudBlue.opacity(0.1))
|
||||
)
|
||||
}
|
||||
.disabled(loginRequest == nil)
|
||||
}
|
||||
}
|
||||
.padding(.top, 4)
|
||||
}
|
||||
.sheet(isPresented: $presentBrowser, onDismiss: {
|
||||
Task {
|
||||
@@ -144,26 +140,26 @@ struct V2LoginView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func sendLoginV2Request() async -> NetworkError? {
|
||||
let (req, error) = await NextcloudApi.loginV2Request()
|
||||
self.loginRequest = req
|
||||
return error
|
||||
}
|
||||
|
||||
|
||||
func fetchLoginV2Response() async -> (LoginV2Response?, NetworkError?) {
|
||||
guard let loginRequest = loginRequest else { return (nil, .invalidRequest) }
|
||||
return await NextcloudApi.loginV2Response(req: loginRequest)
|
||||
}
|
||||
|
||||
|
||||
func checkLogin(response: LoginV2Response?, error: NetworkError?) {
|
||||
if let error = error {
|
||||
alertMessage = "Login failed. Please login via the browser and try again. (\(error.localizedDescription))"
|
||||
alertMessage = String(localized: "Login failed. Please login via the browser and try again.")
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
guard let response = response else {
|
||||
alertMessage = "Login failed. Please login via the browser and try again."
|
||||
alertMessage = String(localized: "Login failed. Please login via the browser and try again.")
|
||||
showAlert = true
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user