mirror of
https://github.com/ProtonMail/ios-mail.git
synced 2026-06-14 09:54:45 +00:00
151 lines
5.2 KiB
Swift
151 lines
5.2 KiB
Swift
// Copyright (c) 2025 Proton Technologies AG
|
|
//
|
|
// This file is part of Proton Mail.
|
|
//
|
|
// Proton Mail is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Proton Mail is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Proton Mail. If not, see https://www.gnu.org/licenses/.
|
|
|
|
import InboxCore
|
|
import InboxDesignSystem
|
|
import ProtonUIFoundations
|
|
import SwiftUI
|
|
|
|
public struct PINLockScreen: View {
|
|
@StateObject var store: PINLockStateStore
|
|
@Binding var error: PINAuthenticationError?
|
|
@FocusState var isFocused: Bool
|
|
|
|
init(
|
|
state: PINLockState,
|
|
error: Binding<PINAuthenticationError?>,
|
|
output: @escaping (PINLockScreenOutput) -> Void
|
|
) {
|
|
self._store = .init(wrappedValue: .init(state: state, output: output))
|
|
self._error = error
|
|
}
|
|
|
|
public init(
|
|
error: Binding<PINAuthenticationError?>,
|
|
isLogoutButtonVisible: Bool = true,
|
|
output: @escaping (PINLockScreenOutput) -> Void
|
|
) {
|
|
self.init(
|
|
state: .init(isLogoutButtonVisible: isLogoutButtonVisible, pin: .empty),
|
|
error: error,
|
|
output: output
|
|
)
|
|
}
|
|
|
|
public var body: some View {
|
|
GeometryReader { geometry in
|
|
ZStack(alignment: .top) {
|
|
BlurredCoverView(showLogo: false)
|
|
VStack(alignment: .center, spacing: .zero) {
|
|
ScrollView {
|
|
VStack(alignment: .center, spacing: .zero) {
|
|
Image
|
|
.protonLogo(size: 120)
|
|
.padding(.top, geometry.size.height * 0.20)
|
|
Text(L10n.PINLock.title)
|
|
.foregroundStyle(DS.Color.Text.norm)
|
|
.font(.title)
|
|
.fontWeight(.semibold)
|
|
.padding(.top, DS.Spacing.huge)
|
|
.padding(.bottom, DS.Spacing.standard)
|
|
|
|
subtitle
|
|
|
|
SecureInput(configuration: .pinLock, text: pinBinding, isSecure: .readonly { true })
|
|
.focused($isFocused)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
.padding(.top, DS.Spacing.huge)
|
|
}
|
|
}
|
|
|
|
Spacer()
|
|
confirmButton
|
|
.padding([.horizontal, .bottom], DS.Spacing.extraLarge)
|
|
}
|
|
HStack {
|
|
Spacer()
|
|
|
|
if store.state.isLogoutButtonVisible {
|
|
Button(action: { store.handle(action: .signOutTapped) }) {
|
|
Text(L10n.PINLock.signOut)
|
|
.foregroundStyle(DS.Color.Text.norm)
|
|
}
|
|
.padding(.trailing, DS.Spacing.large)
|
|
}
|
|
}
|
|
}
|
|
.alert(model: $store.state.alert)
|
|
.onAppear {
|
|
isFocused = true
|
|
}
|
|
.onChange(of: error) { _, description in
|
|
if let description {
|
|
store.handle(action: .error(description))
|
|
}
|
|
}
|
|
.onChange(of: store.state.error) { _, newValue in
|
|
error = newValue
|
|
}
|
|
.sensoryFeedback(.error, trigger: store.state.error) { _, newValue in
|
|
newValue != nil
|
|
}
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var subtitle: some View {
|
|
ZStack {
|
|
if let error = store.state.error?.humanReadable {
|
|
Text(error)
|
|
.font(.callout)
|
|
.foregroundStyle(DS.Color.Notification.error)
|
|
.multilineTextAlignment(.center)
|
|
.transition(.identity)
|
|
} else {
|
|
Text(L10n.PINLock.subtitle)
|
|
.font(.callout)
|
|
.foregroundStyle(DS.Color.Text.weak)
|
|
.transition(.identity)
|
|
}
|
|
}
|
|
.animation(.easeInOut(duration: 0.2), value: store.state.error)
|
|
}
|
|
|
|
private var pinBinding: Binding<String> {
|
|
.init(
|
|
get: { store.state.pin.toString },
|
|
set: { newValue in store.handle(action: .pinEntered(.init(text: newValue))) }
|
|
)
|
|
}
|
|
|
|
private var confirmButton: some View {
|
|
Button(
|
|
action: { store.handle(action: .confirmTapped) },
|
|
label: { Text(CommonL10n.confirm) }
|
|
)
|
|
.buttonStyle(BigButtonStyle())
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
PINLockScreen(
|
|
state: .init(isLogoutButtonVisible: true, pin: .empty),
|
|
error: .readonly(get: { nil }),
|
|
output: { _ in }
|
|
)
|
|
}
|