Просмотр исходного кода

feat(auth): 优化 AuthModalView 键盘焦点流、校验提示与提交状态

bob.yuxinyang 1 месяц назад
Родитель
Сommit
de5d7ffbc7
1 измененных файлов с 98 добавлено и 17 удалено
  1. 98 17
      CelestiaTrace/Views/Profile/Auth/AuthModalView.swift

+ 98 - 17
CelestiaTrace/Views/Profile/Auth/AuthModalView.swift

@@ -11,11 +11,20 @@ struct AuthModalView: View {
     @State private var passwordInput: String = ""
     @State private var passwordInput: String = ""
     @State private var confirmPasswordInput: String = ""
     @State private var confirmPasswordInput: String = ""
     @State private var errorMessage: String?
     @State private var errorMessage: String?
+    @State private var isSubmitting = false
+    @FocusState private var focusedField: AuthField?
     
     
     enum AuthTab {
     enum AuthTab {
         case login
         case login
         case register
         case register
     }
     }
+
+    private enum AuthField: Hashable {
+        case username
+        case identifier
+        case password
+        case confirmPassword
+    }
     
     
     var body: some View {
     var body: some View {
         ZStack {
         ZStack {
@@ -65,21 +74,31 @@ struct AuthModalView: View {
                         inputField(
                         inputField(
                             icon: "person",
                             icon: "person",
                             placeholder: "设置昵称 (如: 星痕探索者)",
                             placeholder: "设置昵称 (如: 星痕探索者)",
-                            text: $usernameInput
+                            text: $usernameInput,
+                            field: .username,
+                            contentType: .nickname,
+                            keyboardType: .default,
+                            submitLabel: .next
                         )
                         )
                     }
                     }
                     
                     
                     inputField(
                     inputField(
                         icon: "envelope",
                         icon: "envelope",
                         placeholder: "手机号或电子邮箱",
                         placeholder: "手机号或电子邮箱",
-                        text: $identifierInput
+                        text: $identifierInput,
+                        field: .identifier,
+                        contentType: .username,
+                        submitLabel: .next
                     )
                     )
                     
                     
                     inputField(
                     inputField(
                         icon: "lock",
                         icon: "lock",
                         placeholder: "密码 (至少 6 位)",
                         placeholder: "密码 (至少 6 位)",
                         text: $passwordInput,
                         text: $passwordInput,
-                        isSecure: true
+                        field: .password,
+                        isSecure: true,
+                        contentType: selectedTab == .login ? .password : .newPassword,
+                        submitLabel: selectedTab == .login ? .go : .next
                     )
                     )
                     
                     
                     if selectedTab == .register {
                     if selectedTab == .register {
@@ -87,7 +106,10 @@ struct AuthModalView: View {
                             icon: "lock.shield",
                             icon: "lock.shield",
                             placeholder: "再次确认密码",
                             placeholder: "再次确认密码",
                             text: $confirmPasswordInput,
                             text: $confirmPasswordInput,
-                            isSecure: true
+                            field: .confirmPassword,
+                            isSecure: true,
+                            contentType: .newPassword,
+                            submitLabel: .go
                         )
                         )
                     }
                     }
                     
                     
@@ -108,11 +130,12 @@ struct AuthModalView: View {
                 Button {
                 Button {
                     handleAuthSubmit()
                     handleAuthSubmit()
                 } label: {
                 } label: {
-                    if authManager.isProcessing {
-                        ProgressView()
-                            .tint(Color.spaceBlack)
-                    } else {
-                        Text(selectedTab == .login ? "安全登录" : "注册并自动登录")
+                    HStack(spacing: 8) {
+                        if isSubmitting {
+                            ProgressView()
+                                .tint(Color.spaceBlack)
+                        }
+                        Text(submitButtonTitle)
                             .font(.system(size: 15, weight: .semibold))
                             .font(.system(size: 15, weight: .semibold))
                             .foregroundStyle(Color.spaceBlack)
                             .foregroundStyle(Color.spaceBlack)
                     }
                     }
@@ -123,9 +146,10 @@ struct AuthModalView: View {
                 .cornerRadius(8)
                 .cornerRadius(8)
                 .padding(.horizontal, 24)
                 .padding(.horizontal, 24)
                 .padding(.bottom, 32)
                 .padding(.bottom, 32)
-                .disabled(authManager.isProcessing)
+                .disabled(isSubmitting)
             }
             }
         }
         }
+        .interactiveDismissDisabled(isSubmitting)
     }
     }
     
     
     private func tabButton(title: String, tab: AuthTab) -> some View {
     private func tabButton(title: String, tab: AuthTab) -> some View {
@@ -133,6 +157,7 @@ struct AuthModalView: View {
             withAnimation(.snappy) {
             withAnimation(.snappy) {
                 selectedTab = tab
                 selectedTab = tab
                 errorMessage = nil
                 errorMessage = nil
+                focusedField = nil
             }
             }
         } label: {
         } label: {
             Text(title)
             Text(title)
@@ -148,7 +173,16 @@ struct AuthModalView: View {
         }
         }
     }
     }
     
     
-    private func inputField(icon: String, placeholder: String, text: Binding<String>, isSecure: Bool = false) -> some View {
+    private func inputField(
+        icon: String,
+        placeholder: String,
+        text: Binding<String>,
+        field: AuthField,
+        isSecure: Bool = false,
+        contentType: UITextContentType?,
+        keyboardType: UIKeyboardType = .asciiCapable,
+        submitLabel: SubmitLabel
+    ) -> some View {
         HStack(spacing: 12) {
         HStack(spacing: 12) {
             Image(systemName: icon)
             Image(systemName: icon)
                 .font(.system(size: 14))
                 .font(.system(size: 14))
@@ -158,11 +192,23 @@ struct AuthModalView: View {
             if isSecure {
             if isSecure {
                 SecureField(placeholder, text: text)
                 SecureField(placeholder, text: text)
                     .font(.system(size: 14))
                     .font(.system(size: 14))
+                    .textContentType(contentType)
+                    .keyboardType(keyboardType)
+                    .textInputAutocapitalization(.never)
+                    .autocorrectionDisabled()
+                    .focused($focusedField, equals: field)
+                    .submitLabel(submitLabel)
+                    .onSubmit { handleSubmit(from: field) }
             } else {
             } else {
                 TextField(placeholder, text: text)
                 TextField(placeholder, text: text)
                     .font(.system(size: 14))
                     .font(.system(size: 14))
-                    .autocapitalization(.none)
-                    .disableAutocorrection(true)
+                    .textContentType(contentType)
+                    .keyboardType(keyboardType)
+                    .textInputAutocapitalization(.never)
+                    .autocorrectionDisabled()
+                    .focused($focusedField, equals: field)
+                    .submitLabel(submitLabel)
+                    .onSubmit { handleSubmit(from: field) }
             }
             }
         }
         }
         .padding(.horizontal, 14)
         .padding(.horizontal, 14)
@@ -170,21 +216,48 @@ struct AuthModalView: View {
         .background(Color.cardBackground.opacity(0.3))
         .background(Color.cardBackground.opacity(0.3))
         .businessBorder(cornerRadius: 8)
         .businessBorder(cornerRadius: 8)
     }
     }
+
+    private var submitButtonTitle: String {
+        if isSubmitting {
+            return selectedTab == .login ? "正在登录…" : "正在注册…"
+        }
+        return selectedTab == .login ? "安全登录" : "注册并自动登录"
+    }
+
+    private func handleSubmit(from field: AuthField) {
+        switch field {
+        case .username:
+            focusedField = .identifier
+        case .identifier:
+            focusedField = .password
+        case .password where selectedTab == .register:
+            focusedField = .confirmPassword
+        case .password, .confirmPassword:
+            handleAuthSubmit()
+        }
+    }
     
     
     private func handleAuthSubmit() {
     private func handleAuthSubmit() {
+        guard !isSubmitting else { return }
         errorMessage = nil
         errorMessage = nil
         
         
         if selectedTab == .login {
         if selectedTab == .login {
-            guard !identifierInput.trimmingCharacters(in: .whitespaces).isEmpty else {
+            let identifier = identifierInput.trimmingCharacters(in: .whitespacesAndNewlines)
+            guard !identifier.isEmpty else {
                 errorMessage = "请输入手机号或邮箱"
                 errorMessage = "请输入手机号或邮箱"
+                focusedField = .identifier
                 return
                 return
             }
             }
             guard passwordInput.count >= 6 else {
             guard passwordInput.count >= 6 else {
                 errorMessage = "密码不能少于 6 位"
                 errorMessage = "密码不能少于 6 位"
+                focusedField = .password
                 return
                 return
             }
             }
             
             
-            authManager.login(identifier: identifierInput, password: passwordInput) { success, err in
+            focusedField = nil
+            isSubmitting = true
+            authManager.login(identifier: identifier, password: passwordInput) { success, err in
+                isSubmitting = false
                 if success {
                 if success {
                     dismiss()
                     dismiss()
                 } else {
                 } else {
@@ -195,22 +268,30 @@ struct AuthModalView: View {
             let name = usernameInput.trimmingCharacters(in: .whitespaces)
             let name = usernameInput.trimmingCharacters(in: .whitespaces)
             guard !name.isEmpty else {
             guard !name.isEmpty else {
                 errorMessage = "请输入用户昵称"
                 errorMessage = "请输入用户昵称"
+                focusedField = .username
                 return
                 return
             }
             }
-            guard !identifierInput.trimmingCharacters(in: .whitespaces).isEmpty else {
+            let identifier = identifierInput.trimmingCharacters(in: .whitespacesAndNewlines)
+            guard !identifier.isEmpty else {
                 errorMessage = "请输入注册手机号或邮箱"
                 errorMessage = "请输入注册手机号或邮箱"
+                focusedField = .identifier
                 return
                 return
             }
             }
             guard passwordInput.count >= 6 else {
             guard passwordInput.count >= 6 else {
                 errorMessage = "密码不能少于 6 位"
                 errorMessage = "密码不能少于 6 位"
+                focusedField = .password
                 return
                 return
             }
             }
             guard passwordInput == confirmPasswordInput else {
             guard passwordInput == confirmPasswordInput else {
                 errorMessage = "两次输入的密码不一致"
                 errorMessage = "两次输入的密码不一致"
+                focusedField = .confirmPassword
                 return
                 return
             }
             }
             
             
-            authManager.register(username: name, identifier: identifierInput, password: passwordInput) { success, err in
+            focusedField = nil
+            isSubmitting = true
+            authManager.register(username: name, identifier: identifier, password: passwordInput) { success, err in
+                isSubmitting = false
                 if success {
                 if success {
                     dismiss()
                     dismiss()
                 } else {
                 } else {