Предыдущий коммит случайно включил изменения из рабочей директории: упрощение GlassModifier, GlassModifiers, RosettaTabBar, ButtonStyles, GlassCard и других файлов, что сломало iOS 26 glass-эффекты и внешний вид tab bar. Восстановлены оригинальные файлы из состояния до этих изменений. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
175 lines
5.5 KiB
Swift
175 lines
5.5 KiB
Swift
import SwiftUI
|
|
import Lottie
|
|
|
|
struct WelcomeView: View {
|
|
let onGenerateSeed: () -> Void
|
|
let onImportSeed: () -> Void
|
|
var onBack: (() -> Void)?
|
|
|
|
@State private var isVisible: Bool
|
|
private static var hasAnimated = false
|
|
|
|
init(onGenerateSeed: @escaping () -> Void, onImportSeed: @escaping () -> Void, onBack: (() -> Void)? = nil) {
|
|
self.onGenerateSeed = onGenerateSeed
|
|
self.onImportSeed = onImportSeed
|
|
self.onBack = onBack
|
|
_isVisible = State(initialValue: Self.hasAnimated)
|
|
}
|
|
|
|
var body: some View {
|
|
ZStack(alignment: .topLeading) {
|
|
VStack(spacing: 0) {
|
|
Spacer()
|
|
|
|
lockAnimation
|
|
.padding(.bottom, 32)
|
|
|
|
titleSection
|
|
.padding(.bottom, 16)
|
|
|
|
subtitleSection
|
|
.padding(.bottom, 24)
|
|
|
|
featureBadges
|
|
.padding(.bottom, 32)
|
|
|
|
Spacer()
|
|
Spacer().frame(height: 16)
|
|
|
|
buttonsSection
|
|
.padding(.horizontal, 24)
|
|
.padding(.bottom, 16)
|
|
}
|
|
|
|
// Back button (only shows when coming from Unlock screen)
|
|
if let onBack {
|
|
GlassBackButton(action: onBack)
|
|
.padding(.leading, 12)
|
|
.padding(.top, 8)
|
|
.accessibilityLabel("Back")
|
|
}
|
|
}
|
|
.onAppear {
|
|
guard !isVisible else { return }
|
|
withAnimation(.easeOut(duration: 0.5)) { isVisible = true }
|
|
Self.hasAnimated = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Lock Animation
|
|
|
|
private extension WelcomeView {
|
|
var lockAnimation: some View {
|
|
LottieView(
|
|
animationName: "lock",
|
|
loopMode: .loop,
|
|
animationSpeed: 1.0
|
|
)
|
|
.frame(width: 120, height: 120)
|
|
.opacity(isVisible ? 1.0 : 0.0)
|
|
.scaleEffect(isVisible ? 1.0 : 0.5)
|
|
.animation(.spring(response: 0.5, dampingFraction: 0.7).delay(0.1), value: isVisible)
|
|
.accessibilityHidden(true)
|
|
}
|
|
}
|
|
|
|
// MARK: - Text Sections
|
|
|
|
private extension WelcomeView {
|
|
var titleSection: some View {
|
|
Text("Your Keys,\nYour Messages")
|
|
.font(.system(size: 32, weight: .bold))
|
|
.foregroundStyle(.white)
|
|
.multilineTextAlignment(.center)
|
|
.opacity(isVisible ? 1.0 : 0.0)
|
|
.offset(y: isVisible ? 0 : 16)
|
|
.animation(.easeOut(duration: 0.4).delay(0.2), value: isVisible)
|
|
}
|
|
|
|
var subtitleSection: some View {
|
|
Text("Secure messaging with\ncryptographic keys")
|
|
.font(.system(size: 16))
|
|
.foregroundStyle(Color.white.opacity(0.7))
|
|
.multilineTextAlignment(.center)
|
|
.opacity(isVisible ? 1.0 : 0.0)
|
|
.offset(y: isVisible ? 0 : 12)
|
|
.animation(.easeOut(duration: 0.4).delay(0.3), value: isVisible)
|
|
}
|
|
}
|
|
|
|
// MARK: - Feature Badges
|
|
|
|
private extension WelcomeView {
|
|
var featureBadges: some View {
|
|
HStack(spacing: 24) {
|
|
featureBadge(icon: "lock.shield.fill", label: "Encrypted", index: 0)
|
|
featureBadge(icon: "person.crop.circle.badge.minus", label: "No Phone", index: 1)
|
|
featureBadge(icon: "key.fill", label: "Your Keys", index: 2)
|
|
}
|
|
.opacity(isVisible ? 1.0 : 0.0)
|
|
.animation(.easeOut(duration: 0.5).delay(0.4), value: isVisible)
|
|
}
|
|
|
|
func featureBadge(icon: String, label: String, index: Int) -> some View {
|
|
VStack(spacing: 8) {
|
|
GlassCard(cornerRadius: 24, fillOpacity: 0.12) {
|
|
Image(systemName: icon)
|
|
.font(.system(size: 22))
|
|
.foregroundStyle(RosettaColors.primaryBlue)
|
|
.frame(width: 48, height: 48)
|
|
}
|
|
|
|
Text(label)
|
|
.font(.system(size: 13, weight: .medium))
|
|
.foregroundStyle(Color.white.opacity(0.7))
|
|
}
|
|
.accessibilityElement(children: .combine)
|
|
.accessibilityLabel(label)
|
|
}
|
|
}
|
|
|
|
// MARK: - Buttons
|
|
|
|
private extension WelcomeView {
|
|
var buttonsSection: some View {
|
|
VStack(spacing: 12) {
|
|
Button(action: onGenerateSeed) {
|
|
HStack(spacing: 10) {
|
|
Image(systemName: "sparkles")
|
|
.font(.system(size: 18))
|
|
Text("Generate New Seed Phrase")
|
|
.font(.system(size: 16, weight: .semibold))
|
|
}
|
|
.foregroundStyle(.white)
|
|
.frame(maxWidth: .infinity)
|
|
.frame(height: 56)
|
|
}
|
|
.buttonStyle(RosettaPrimaryButtonStyle())
|
|
.shine()
|
|
.accessibilityHint("Creates a new encrypted account")
|
|
|
|
Button(action: onImportSeed) {
|
|
HStack(spacing: 8) {
|
|
Image(systemName: "square.and.arrow.down")
|
|
.font(.system(size: 16))
|
|
Text("I Already Have a Seed Phrase")
|
|
.font(.system(size: 15, weight: .medium))
|
|
}
|
|
.foregroundStyle(RosettaColors.primaryBlue)
|
|
.frame(maxWidth: .infinity)
|
|
.frame(height: 44)
|
|
}
|
|
.accessibilityHint("Restores an existing account")
|
|
}
|
|
.opacity(isVisible ? 1.0 : 0.0)
|
|
.animation(.easeOut(duration: 0.4).delay(0.6), value: isVisible)
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
WelcomeView(onGenerateSeed: {}, onImportSeed: {}, onBack: {})
|
|
.preferredColorScheme(.dark)
|
|
.background(RosettaColors.authBackground)
|
|
}
|