import SwiftUI struct LoginView: View { @Binding var root: RootFlow @Binding var selectedTab: MainTab let tokenStore: TokenStore @Binding var appState: AppState @Environment(\.colorScheme) var colorScheme var shouldPrepareEntryAnimation: Bool = false var authEntryAnimationToken: Int = 0 let navigate: (Route) -> Void @State var heroVisible = true @State var textVisible = true @State var buttonVisible = true @State var lastAnimatedToken = 0 @ViewBuilder private var logoImage: some View { SwiftUI.Image("pedifoods") .resizable() } @ViewBuilder private var pinHeroImage: some View { SwiftUI.Image("pin_image_app") .resizable() } var body: some View { GeometryReader { geo in let heroHeight = max(360, geo.size.height * 0.44) let logoTopInset = max(0, (geo.size.height - 180) / 2) ZStack(alignment: .top) { (colorScheme == .dark ? Color.black : AppColors.backgroundLight) .ignoresSafeArea() pinHeroImage .scaledToFill() .frame(height: heroHeight + 80) .offset(y: heroVisible ? -60 : -(heroHeight + 220)) .mask( LinearGradient( colors: [.black, .black, .black.opacity(0.0)], startPoint: .top, endPoint: .bottom ) ) VStack(spacing: 18) { Spacer().frame(height: logoTopInset) logoImage .scaledToFit() .frame(height: 180) Text("Sua vontade, no seu tempo.\nTudo o que você precisa, em um toque.") .font(AppTypography.heading25) .foregroundStyle(colorScheme == .dark ? AppColors.textInverse : AppColors.textPrimary) .multilineTextAlignment(.leading) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 28) .opacity(textVisible ? 1.0 : 0.0) .offset(y: textVisible ? 0 : 24) Spacer().frame(height: 40) Button { navigate(.loginEmail) } label: { PrimaryButtonLabel(title: "ENTRAR") } .padding(.horizontal, 28) .tint(AppColors.tertiary) .offset(y: buttonVisible ? 0 : 140) .opacity(buttonVisible ? 1.0 : 0.0) .buttonStyle(.plain) HStack(spacing: 6) { Text("Não tem conta ainda?") .foregroundStyle(colorScheme == .dark ? AppColors.textInverse.opacity(0.9) : AppColors.textPrimary) Button("Criar conta") { navigate(.registration) } .buttonStyle(.plain) .foregroundStyle(AppColors.primary) } .font(AppTypography.body) .opacity(textVisible ? 1.0 : 0.0) .offset(y: textVisible ? 0 : 24) Spacer().frame(height: 12) } } .ignoresSafeArea() .onAppear { if shouldPrepareEntryAnimation { applyHiddenStateWithoutAnimation() } else { showFinalStateWithoutAnimation() } } .task(id: authEntryAnimationToken) { await runEntryAnimationIfNeeded(for: authEntryAnimationToken) } } } @MainActor private func applyHiddenStateWithoutAnimation() { heroVisible = false textVisible = false buttonVisible = false } @MainActor private func showFinalStateWithoutAnimation() { heroVisible = true textVisible = true buttonVisible = true } @MainActor private func runEntryAnimationIfNeeded(for token: Int) async { guard token > 0 else { return } guard token != lastAnimatedToken else { return } lastAnimatedToken = token applyHiddenStateWithoutAnimation() try? await Task.sleep(nanoseconds: 40_000_000) withAnimation(.spring(response: 0.64, dampingFraction: 0.9)) { heroVisible = true } try? await Task.sleep(nanoseconds: 160_000_000) withAnimation(.easeOut(duration: 0.42)) { textVisible = true } try? await Task.sleep(nanoseconds: 150_000_000) withAnimation(.spring(response: 0.52, dampingFraction: 0.86)) { buttonVisible = true } } }