Files
Luma/Sources/Shared/UI/LoginView.swift
T
wt 6cf74618d7
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s
added .KeyboardType(.emailAddress) to xmppaddress TextField in
LoginView.swift
2026-08-21 16:21:00 +07:00

162 lines
6.2 KiB
Swift

import SwiftUI
struct LoginView: View {
@ObservedObject var model: AppModel
@State private var jid = ""
@State private var password = ""
@State private var displayName = ""
@State private var resource = ""
@State private var showAdvanced = false
@State private var manualHost = ""
@State private var manualPort = ""
@State private var directTLS = false
var body: some View {
ZStack {
LinearGradient(
colors: [
Color(red: 0.08, green: 0.38, blue: 0.78),
Color(red: 0.12, green: 0.62, blue: 0.96),
Color(red: 0.44, green: 0.84, blue: 0.96),
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
Circle()
.fill(.white.opacity(0.12))
.frame(width: 420, height: 420)
.offset(x: -210, y: -260)
ScrollView {
VStack(spacing: 28) {
brand
loginCard
}
.frame(maxWidth: 460)
.padding(.horizontal, 24)
.padding(.vertical, 48)
.frame(maxWidth: .infinity)
}
}
}
private var brand: some View {
VStack(spacing: 14) {
ZStack {
RoundedRectangle(cornerRadius: 26, style: .continuous)
.fill(.white)
.frame(width: 88, height: 88)
.shadow(color: .black.opacity(0.16), radius: 22, y: 10)
Image(systemName: "bubble.left.and.bubble.right.fill")
.font(.system(size: 39, weight: .semibold))
.foregroundStyle(
LinearGradient(
colors: [.blue, .cyan],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
}
Text("Luma")
.font(.system(size: 42, weight: .bold, design: .rounded))
.foregroundStyle(.white)
Text("Приватный XMPP без привязки к одному серверу")
.font(.subheadline)
.foregroundStyle(.white.opacity(0.86))
.multilineTextAlignment(.center)
}
}
private var loginCard: some View {
VStack(spacing: 18) {
VStack(alignment: .leading, spacing: 7) {
Text("XMPP-аккаунт")
.font(.headline)
TextField("you@example.org", text: $jid)
.keyboardType(.emailAddress)
.textFieldStyle(.roundedBorder)
.disableAutocorrection(true)
}
VStack(alignment: .leading, spacing: 7) {
Text("Пароль")
.font(.headline)
SecureField("Пароль", text: $password)
.textFieldStyle(.roundedBorder)
}
DisclosureGroup("Дополнительные настройки", isExpanded: $showAdvanced) {
VStack(spacing: 14) {
TextField("Отображаемое имя", text: $displayName)
.textFieldStyle(.roundedBorder)
TextField("Автоматически (Luma-XXXX)", text: $resource)
.textFieldStyle(.roundedBorder)
Text(
"Оставьте пустым — Luma создаст уникальный ресурс для этого устройства, чтобы macOS и iOS не конфликтовали между собой."
)
.font(.caption)
.foregroundStyle(.secondary)
TextField("Сервер вручную (необязательно)", text: $manualHost)
.textFieldStyle(.roundedBorder)
TextField(
directTLS ? "Порт, обычно 5223" : "Порт, обычно 5222", text: $manualPort
)
.textFieldStyle(.roundedBorder)
Toggle("Direct TLS", isOn: $directTLS)
Text(
"Без ручного адреса Luma использует DNS SRV и автоматически выбирает STARTTLS/direct TLS."
)
.font(.caption)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
}
.padding(.top, 14)
}
Button {
Task { await signIn() }
} label: {
HStack {
if model.isAuthenticating {
ProgressView().controlSize(.small)
}
Text(model.isAuthenticating ? "Подключение…" : "Войти")
.fontWeight(.semibold)
}
.frame(maxWidth: .infinity)
.frame(height: 34)
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
.disabled(model.isAuthenticating)
Label(
"Сообщения отправляются с OMEMO. Пароль хранится только в Keychain.",
systemImage: "lock.shield.fill"
)
.font(.caption)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
}
.padding(26)
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 28, style: .continuous))
.shadow(color: .black.opacity(0.14), radius: 24, y: 12)
}
private func signIn() async {
let port = Int(manualPort.trimmingCharacters(in: .whitespacesAndNewlines))
let account = AccountConfiguration(
jid: jid,
displayName: displayName,
resource: resource,
manualHost: manualHost,
manualPort: port,
usesDirectTLS: directTLS
)
await model.signIn(account: account, password: password)
}
}