You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

237 lines
6.2 KiB

import SwiftUI
// MARK: -
struct ValidationView: View {
let validationState: ValidationState
let message: String?
let showIcon: Bool
init(
validationState: ValidationState,
message: String? = nil,
showIcon: Bool = true
) {
self.validationState = validationState
self.message = message
self.showIcon = showIcon
}
var body: some View {
if validationState != .none {
HStack(spacing: 6) {
if showIcon {
Image(systemName: iconName)
.font(.caption)
.foregroundColor(iconColor)
}
if let message = message {
Text(message)
.font(.caption)
.foregroundColor(textColor)
}
Spacer()
}
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 6)
.fill(backgroundColor)
)
}
}
private var iconName: String {
switch validationState {
case .none:
return ""
case .valid:
return "checkmark.circle"
case .warning:
return "exclamationmark.triangle"
case .error:
return "xmark.circle"
case .info:
return "info.circle"
}
}
private var iconColor: Color {
switch validationState {
case .none:
return .clear
case .valid:
return .green
case .warning:
return .orange
case .error:
return .red
case .info:
return .blue
}
}
private var textColor: Color {
switch validationState {
case .none:
return .clear
case .valid:
return .green
case .warning:
return .orange
case .error:
return .red
case .info:
return .blue
}
}
private var backgroundColor: Color {
switch validationState {
case .none:
return .clear
case .valid:
return .green.opacity(0.1)
case .warning:
return .orange.opacity(0.1)
case .error:
return .red.opacity(0.1)
case .info:
return .blue.opacity(0.1)
}
}
}
// MARK: -
enum ValidationState {
case none
case valid
case warning
case error
case info
}
// MARK: -
extension ValidationView {
static func success(message: String, showIcon: Bool = true) -> ValidationView {
ValidationView(
validationState: .valid,
message: message,
showIcon: showIcon
)
}
static func warning(message: String, showIcon: Bool = true) -> ValidationView {
ValidationView(
validationState: .warning,
message: message,
showIcon: showIcon
)
}
static func error(message: String, showIcon: Bool = true) -> ValidationView {
ValidationView(
validationState: .error,
message: message,
showIcon: showIcon
)
}
static func info(message: String, showIcon: Bool = true) -> ValidationView {
ValidationView(
validationState: .info,
message: message,
showIcon: showIcon
)
}
}
// MARK: -
struct CharacterCountValidation: View {
let currentCount: Int
let maxCount: Int
let warningThreshold: Double
init(
currentCount: Int,
maxCount: Int,
warningThreshold: Double = 0.9
) {
self.currentCount = currentCount
self.maxCount = maxCount
self.warningThreshold = warningThreshold
}
var body: some View {
HStack {
Spacer()
if currentCount >= maxCount {
ValidationView.error(message: NSLocalizedString("max_characters_reached", comment: "Maximum characters reached"))
} else if currentCount >= Int(Double(maxCount) * warningThreshold) {
ValidationView.warning(message: NSLocalizedString("near_character_limit", comment: "Near character limit"))
} else {
Text("\(currentCount)/\(maxCount)")
.font(.caption)
.foregroundColor(.secondary)
}
}
}
}
// MARK: -
struct RequiredFieldValidation: View {
let fieldName: String
let isEmpty: Bool
var body: some View {
if isEmpty {
ValidationView.error(message: String(format: NSLocalizedString("field_required", comment: "Field is required"), fieldName))
}
}
}
// MARK: -
struct FormatValidation: View {
let fieldName: String
let isValid: Bool
let errorMessage: String?
init(
fieldName: String,
isValid: Bool,
errorMessage: String? = nil
) {
self.fieldName = fieldName
self.isValid = isValid
self.errorMessage = errorMessage
}
var body: some View {
if !isValid {
ValidationView.error(
message: errorMessage ?? String(format: NSLocalizedString("field_format_incorrect", comment: "Field format is incorrect"), fieldName)
)
}
}
}
#Preview {
VStack(spacing: 16) {
ValidationView.success(message: "验证成功!")
ValidationView.warning(message: "这是一个警告")
ValidationView.error(message: "这是一个错误")
ValidationView.info(message: "这是一个提示")
CharacterCountValidation(currentCount: 95, maxCount: 100)
CharacterCountValidation(currentCount: 100, maxCount: 100)
RequiredFieldValidation(fieldName: "用户名", isEmpty: true)
RequiredFieldValidation(fieldName: "邮箱", isEmpty: false)
FormatValidation(fieldName: "邮箱地址", isValid: false)
FormatValidation(fieldName: "电话号码", isValid: true)
}
.padding()
}