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>
193 lines
6.1 KiB
Swift
193 lines
6.1 KiB
Swift
//
|
|
// OnboardingView.swift
|
|
// Nextcloud Cookbook iOS Client
|
|
//
|
|
// Created by Vincent Meilinger on 15.09.23.
|
|
//
|
|
|
|
import Foundation
|
|
import OSLog
|
|
import SwiftUI
|
|
|
|
struct OnboardingView: View {
|
|
@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 {
|
|
ScrollView(showsIndicators: false) {
|
|
VStack(spacing: 0) {
|
|
Image("cookbook-icon")
|
|
.resizable()
|
|
.frame(width: 80, height: 80)
|
|
.clipShape(RoundedRectangle(cornerRadius: 18))
|
|
.padding(.top, 48)
|
|
Text("Cookbook Client")
|
|
.font(.largeTitle)
|
|
.bold()
|
|
.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)
|
|
}
|
|
.fontDesign(.rounded)
|
|
.padding()
|
|
.alert(alertMessage, isPresented: $showAlert) {
|
|
Button("Ok", role: .cancel) { }
|
|
}
|
|
}
|
|
.background(Color(uiColor: .systemGroupedBackground).ignoresSafeArea())
|
|
}
|
|
}
|
|
|
|
protocol LoginStage {
|
|
func next() -> Self
|
|
func previous() -> Self
|
|
}
|
|
|
|
enum LoginMethod {
|
|
case v2, token
|
|
}
|
|
|
|
enum TokenLoginStage: LoginStage {
|
|
case serverAddress, userName, appToken, validate
|
|
|
|
func next() -> TokenLoginStage {
|
|
switch self {
|
|
case .serverAddress: return .userName
|
|
case .userName: return .appToken
|
|
case .appToken: return .validate
|
|
case .validate: return .validate
|
|
}
|
|
}
|
|
|
|
func previous() -> TokenLoginStage {
|
|
switch self {
|
|
case .serverAddress: return .serverAddress
|
|
case .userName: return .serverAddress
|
|
case .appToken: return .userName
|
|
case .validate: return .appToken
|
|
}
|
|
}
|
|
}
|
|
|
|
struct LoginLabel: View {
|
|
let text: LocalizedStringKey
|
|
var body: some View {
|
|
Text(text)
|
|
.font(.subheadline)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
|
|
struct BorderedLoginTextField: View {
|
|
var example: LocalizedStringKey
|
|
@Binding var text: String
|
|
|
|
var body: some View {
|
|
TextField(example, text: $text)
|
|
.textFieldStyle(.plain)
|
|
.autocorrectionDisabled()
|
|
.textInputAutocapitalization(.never)
|
|
.padding()
|
|
.background(Color(uiColor: .secondarySystemGroupedBackground))
|
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
|
}
|
|
}
|
|
|
|
struct LoginTextField: View {
|
|
var example: LocalizedStringKey
|
|
@Binding var text: String
|
|
|
|
var body: some View {
|
|
TextField(example, text: $text)
|
|
.textFieldStyle(.plain)
|
|
.autocorrectionDisabled()
|
|
.textInputAutocapitalization(.never)
|
|
.padding()
|
|
.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, spacing: 6) {
|
|
LoginLabel(text: "Server address")
|
|
VStack(alignment: .leading, spacing: 10) {
|
|
HStack {
|
|
Picker(ServerProtocol.https.rawValue, selection: $serverProtocol) {
|
|
ForEach(ServerProtocol.all, id: \.self) {
|
|
Text($0.rawValue)
|
|
}
|
|
}
|
|
.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)
|
|
.padding(10)
|
|
.background(Color(uiColor: .secondarySystemGroupedBackground))
|
|
.clipShape(RoundedRectangle(cornerRadius: 8))
|
|
}
|
|
|
|
Text(userSettings.serverProtocol + userSettings.serverAddress)
|
|
.font(.footnote)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
.padding()
|
|
.background(Color(uiColor: .secondarySystemGroupedBackground))
|
|
.clipShape(RoundedRectangle(cornerRadius: 12))
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ServerAddressField_Preview: PreviewProvider {
|
|
static var previews: some View {
|
|
ServerAddressField()
|
|
.previewLayout(.sizeThatFits)
|
|
.padding()
|
|
}
|
|
}
|