- Gate every layer behind AppLockView when the lock is enabled and lock on launch and on backgrounding; the passcode lives only in the Keychain (AppLockVault) and the enable state defaults to off. - Add the lock section to Settings: enable/setup, change passcode and disable all require the current passcode via a shared passcode sheet; Face ID / Touch ID unlock prompts automatically and can be toggled with passcode confirmation. - Add NSFaceIDUsageDescription to both app targets, cover the passcode policy with tests, guard the feature in verify.sh and document it in SECURITY.md.
55 lines
1.9 KiB
Swift
55 lines
1.9 KiB
Swift
import QuickLook
|
|
import SwiftData
|
|
import SwiftUI
|
|
|
|
struct RootView: View {
|
|
@ObservedObject var model: AppModel
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
if model.isAppLocked {
|
|
// The lock covers every layer: chat list, media viewer and
|
|
// calls stay hidden until the passcode or biometrics succeed.
|
|
AppLockView(model: model)
|
|
} else {
|
|
Group {
|
|
if model.account == nil {
|
|
LoginView(model: model)
|
|
} else {
|
|
MainChatView(model: model)
|
|
.environment(\.modelContext, model.modelContext)
|
|
}
|
|
}
|
|
|
|
if let item = model.mediaViewerItem {
|
|
MediaViewer(item: item, onClose: model.closeMediaViewer)
|
|
.transition(.opacity.combined(with: .scale(scale: 0.98)))
|
|
.zIndex(100)
|
|
}
|
|
|
|
if let call = model.activeCall {
|
|
CallView(model: model, call: call)
|
|
.transition(.opacity.combined(with: .scale(scale: 1.015)))
|
|
.zIndex(200)
|
|
}
|
|
}
|
|
}
|
|
.animation(.easeInOut(duration: 0.22), value: model.account?.id)
|
|
.animation(.easeInOut(duration: 0.2), value: model.mediaViewerItem?.id)
|
|
.animation(.easeInOut(duration: 0.2), value: model.activeCall?.id)
|
|
.alert("Luma", isPresented: errorBinding) {
|
|
Button("OK") { model.clearError() }
|
|
} message: {
|
|
Text(model.errorMessage ?? "Неизвестная ошибка")
|
|
}
|
|
.quickLookPreview($model.previewURL)
|
|
}
|
|
|
|
private var errorBinding: Binding<Bool> {
|
|
Binding(
|
|
get: { model.errorMessage != nil },
|
|
set: { visible in if !visible { model.clearError() } }
|
|
)
|
|
}
|
|
}
|