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:
2026-02-15 10:44:51 +01:00
parent ce2a814e5a
commit c8d9ab7397
4 changed files with 4178 additions and 3886 deletions

View File

@@ -2178,9 +2178,7 @@
}
}
},
"Grocery list state is synced via your Nextcloud server by storing it alongside recipe data." : {
},
"Grocery list state is synced via your Nextcloud server by storing it alongside recipe data.": {},
"Grocery list storage": {
"localizations": {
"de": {
@@ -2252,19 +2250,19 @@
"de": {
"stringUnit": {
"state": "translated",
"value" : "Falls sich der Browser beim Klicken auf den Anmeldebutton nicht öffnet, klicke einfach auf den Button Link kopieren“. Anschließend kann der kopierte Link manuell in den Browser eingefügt werden."
"value": "Falls der Anmelde-Button deinen Browser nicht öffnet, verwende den 'Link kopieren'-Button und füge den Link manuell in deinen Browser ein."
}
},
"es": {
"stringUnit": {
"state": "translated",
"value" : "Si el botón de inicio de sesión no abre tu navegador, usa el botón 'Copiar enlace' y pega el enlace manualmente en tu navegador."
"value": "Si el botón de inicio de sesión no abre tu navegador, usa el botón 'Copiar enlace' y pega el enlace en tu navegador manualmente."
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value" : "Si le bouton de connexion n'ouvre pas votre navigateur, utilisez le bouton 'Copier le lien' et collez le lien manuellement dans votre navigateur."
"value": "Si le bouton de connexion n'ouvre pas votre navigateur, utilisez le bouton 'Copier le lien' et collez le lien dans votre navigateur manuellement."
}
}
}
@@ -2824,6 +2822,7 @@
}
},
"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.": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -2845,6 +2844,28 @@
}
}
},
"Make sure to enter the server address in the form 'example.com', or '<server address>:<port>' when a non-standard port is used.": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Stelle sicher, dass du die Serveradresse im Format 'beispiel.com' oder '<Serveradresse>:<Port>' eingibst, wenn ein nicht-standardmäßiger Port verwendet wird."
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Asegúrate de introducir la dirección del servidor en la forma 'ejemplo.com', o '<dirección del servidor>:<puerto>' cuando se utilice un puerto no estándar."
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Assurez-vous d'entrer l'adresse du serveur sous la forme 'exemple.com', ou '<adresse du serveur>:<port>' lorsqu'un port non standard est utilisé."
}
}
}
},
"Manual": {
"localizations": {
"de": {
@@ -4830,9 +4851,7 @@
}
}
},
"Sync grocery list across devices" : {
},
"Sync grocery list across devices": {},
"System": {
"localizations": {
"de": {
@@ -4856,6 +4875,7 @@
}
},
"Thank you for downloading": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -4877,7 +4897,30 @@
}
}
},
"Thanks for downloading! Sign in to your Nextcloud server to get started.": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Danke fürs Herunterladen! Melde dich bei deinem Nextcloud-Server an, um loszulegen."
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Gracias por descargar. Inicia sesión en tu servidor Nextcloud para comenzar."
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Merci d'avoir téléchargé ! Connectez-vous à votre serveur Nextcloud pour commencer."
}
}
}
},
"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'.": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -4899,6 +4942,28 @@
}
}
},
"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'.": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Der 'Anmelden'-Button öffnet einen Webbrowser. Bitte folge den dort angegebenen Anmeldeanweisungen. Nach erfolgreicher Anmeldung kehre zu dieser App zurück und drücke 'Überprüfen'."
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "El botón 'Iniciar sesión' abrirá un navegador web. Por favor, sigue las instrucciones de inicio de sesión proporcionadas allí. Después de un inicio de sesión exitoso, regresa a esta aplicación y presiona 'Validar'."
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Le bouton 'Connexion' ouvrira un navigateur web. Veuillez suivre les instructions de connexion fournies. Après une connexion réussie, revenez à cette application et appuyez sur 'Valider'."
}
}
}
},
"The recipe could not be imported. Please check the URL and try again.": {
"localizations": {
"de": {
@@ -5035,6 +5100,7 @@
}
},
"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.": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -5650,6 +5716,292 @@
}
}
}
},
"Server address": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Serveradresse"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Dirección del servidor"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Adresse du serveur"
}
}
}
},
"User name": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Benutzername"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Nombre de usuario"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Nom d'utilisateur"
}
}
}
},
"username": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Benutzername"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "nombre de usuario"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "nom d'utilisateur"
}
}
}
},
"App Token": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "App-Token"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Token de la aplicación"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Jeton d'application"
}
}
}
},
"can be generated in security settings of your nextcloud": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "kann in den Sicherheitseinstellungen deiner Nextcloud generiert werden"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "se puede generar en la configuración de seguridad de tu Nextcloud"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "peut être généré dans les paramètres de sécurité de votre Nextcloud"
}
}
}
},
"Error: Could not connect to server.": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Fehler: Verbindung zum Server konnte nicht hergestellt werden."
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Error: No se pudo conectar al servidor."
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Erreur : Impossible de se connecter au serveur."
}
}
}
},
"Please enter a valid server address.": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Bitte eine gültige Serveradresse eingeben."
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Por favor, introduce una dirección de servidor válida."
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Veuillez entrer une adresse de serveur valide."
}
}
}
},
"A network error occurred. Please try again.": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Ein Netzwerkfehler ist aufgetreten. Bitte versuche es erneut."
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Se produjo un error de red. Por favor, inténtalo de nuevo."
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Une erreur réseau s'est produite. Veuillez réessayer."
}
}
}
},
"Unable to reach server. Please check your server address and internet connection.": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Server nicht erreichbar. Bitte überprüfe deine Serveradresse und Internetverbindung."
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "No se puede conectar al servidor. Por favor, verifica la dirección del servidor y tu conexión a internet."
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Impossible de joindre le serveur. Veuillez vérifier l'adresse du serveur et votre connexion internet."
}
}
}
},
"Login failed. Please login via the browser and try again.": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Anmeldung fehlgeschlagen. Bitte melde dich über den Browser an und versuche es erneut."
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Inicio de sesión fallido. Por favor, inicia sesión a través del navegador e inténtalo de nuevo."
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Échec de la connexion. Veuillez vous connecter via le navigateur et réessayer."
}
}
}
},
"Please enter a server address!": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Bitte eine Serveradresse eingeben!"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Por favor, introduce una dirección de servidor."
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Veuillez entrer une adresse de serveur !"
}
}
}
},
"Please enter a user name and app token!": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Bitte Benutzername und App-Token eingeben!"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Por favor, introduce un nombre de usuario y un token de aplicación."
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Veuillez entrer un nom d'utilisateur et un jeton d'application !"
}
}
}
},
"Login failed. Please check your inputs and internet connection.": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Anmeldung fehlgeschlagen. Bitte überprüfe deine Eingaben und Internetverbindung."
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Inicio de sesión fallido. Por favor, verifica tus datos y tu conexión a internet."
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Échec de la connexion. Veuillez vérifier vos identifiants et votre connexion internet."
}
}
}
}
},
"version": "1.1"

View File

@@ -10,41 +10,59 @@ import OSLog
import SwiftUI
struct OnboardingView: View {
@State var selectedTab: Int = 0
@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.")
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)
}
}
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())
}
}
@@ -79,108 +97,45 @@ 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
@@ -192,17 +147,17 @@ struct ServerAddressField: View {
}
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
@@ -212,27 +167,18 @@ struct ServerAddressField: View {
.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)
}
}

View File

@@ -26,25 +26,25 @@ struct TokenLoginView: View {
}
var body: some View {
VStack(alignment: .leading) {
VStack(alignment: .leading, spacing: 16) {
ServerAddressField()
.padding(.bottom)
VStack(alignment: .leading, spacing: 6) {
LoginLabel(text: "User name")
BorderedLoginTextField(example: "username", text: $userSettings.username)
.focused($focusedField, equals: .username)
.textContentType(.username)
.submitLabel(.next)
.padding(.bottom)
}
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)
HStack{
Spacer()
}
Button {
Task {
if await loginCheck(nextcloudLogin: false) {
@@ -52,19 +52,18 @@ struct TokenLoginView: View {
}
}
} label: {
Text("Submit")
.foregroundColor(.white)
.font(.headline)
.padding()
Label("Submit", systemImage: "person.badge.key")
.font(.subheadline)
.fontWeight(.medium)
.frame(maxWidth: .infinity)
.padding(.vertical, 10)
.foregroundStyle(Color.nextcloudBlue)
.background(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.white, lineWidth: 2)
.foregroundColor(.clear)
.fill(Color.nextcloudBlue.opacity(0.1))
)
}
.padding()
Spacer()
}
.padding(.top, 4)
}
.onSubmit {
switch focusedField {
@@ -80,11 +79,11 @@ 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
}

View File

@@ -46,36 +46,35 @@ struct V2LoginView: View {
}
var body: some View {
ScrollView {
VStack(alignment: .leading) {
VStack(alignment: .leading, spacing: 16) {
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)
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.")
}
.font(.footnote)
.foregroundStyle(.secondary)
} title: {
Text("Show help")
.foregroundColor(.white)
.font(.headline)
}.padding()
.font(.subheadline)
}
if loginRequest != nil {
Button("Copy Link") {
Button {
UIPasteboard.general.string = loginRequest!.login
} label: {
Label("Copy Link", systemImage: "doc.on.doc")
.font(.subheadline)
}
.font(.headline)
.foregroundStyle(.white)
.padding()
}
HStack {
HStack(spacing: 12) {
Button {
if UserSettings.shared.serverAddress == "" {
alertMessage = "Please enter a valid server address."
alertMessage = String(localized: "Please enter a valid server address.")
showAlert = true
return
}
@@ -83,55 +82,52 @@ struct V2LoginView: View {
Task {
let error = await sendLoginV2Request()
if let error = error {
alertMessage = "A network error occured (\(error.localizedDescription))."
alertMessage = String(localized: "A network error occurred. Please try again.")
showAlert = true
}
if let loginRequest = loginRequest {
if let _ = 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."
alertMessage = String(localized: "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()
Label("Login", systemImage: "person.badge.key")
.font(.subheadline)
.fontWeight(.medium)
.frame(maxWidth: .infinity)
.padding(.vertical, 10)
.foregroundStyle(Color.nextcloudBlue)
.background(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.white, lineWidth: 2)
.foregroundColor(.clear)
.fill(Color.nextcloudBlue.opacity(0.1))
)
}.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()
Label("Validate", systemImage: "checkmark.circle.fill")
.font(.subheadline)
.fontWeight(.medium)
.frame(maxWidth: .infinity)
.padding(.vertical, 10)
.foregroundStyle(Color.nextcloudBlue)
.background(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.white, lineWidth: 2)
.foregroundColor(.clear)
.fill(Color.nextcloudBlue.opacity(0.1))
)
}
.disabled(loginRequest == nil ? true : false)
.padding()
}
.disabled(loginRequest == nil)
}
}
.padding(.top, 4)
}
.sheet(isPresented: $presentBrowser, onDismiss: {
Task {
@@ -158,12 +154,12 @@ struct V2LoginView: View {
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
}