This commit is contained in:
Daniel Arantes Loverde
2026-03-10 09:19:50 -03:00
parent 728cc94525
commit 5fa98ce358
15 changed files with 570 additions and 94 deletions

View File

@@ -11,12 +11,21 @@ struct AuthFlowView: View {
@Binding var selectedTab: MainTab
let tokenStore: TokenStore
@Binding var appState: AppState
var shouldPrepareLoginEntry: Bool = false
var authEntryAnimationToken: Int = 0
@State var path: [Route] = []
var body: some View {
NavigationStack(path: $path) {
LoginView(root: $root, selectedTab: $selectedTab, tokenStore: tokenStore, appState: $appState) { route in
LoginView(
root: $root,
selectedTab: $selectedTab,
tokenStore: tokenStore,
appState: $appState,
shouldPrepareEntryAnimation: shouldPrepareLoginEntry,
authEntryAnimationToken: authEntryAnimationToken
) { route in
path.append(route)
}
.navigationDestination(for: Route.self) { route in

View File

@@ -6,7 +6,13 @@ struct LoginView: View {
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 {
#if os(Android)
@@ -31,6 +37,7 @@ struct LoginView: View {
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)
@@ -39,7 +46,7 @@ struct LoginView: View {
pinHeroImage
.scaledToFill()
.frame(height: heroHeight + 80)
.offset(y: -60)
.offset(y: heroVisible ? -60 : -(heroHeight + 220))
.mask(
LinearGradient(
colors: [.black, .black, .black.opacity(0.0)],
@@ -49,7 +56,7 @@ struct LoginView: View {
)
VStack(spacing: 18) {
Spacer().frame(height: heroHeight - 100)
Spacer().frame(height: logoTopInset)
logoImage
.scaledToFit()
@@ -61,6 +68,8 @@ struct LoginView: View {
.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)
@@ -71,6 +80,8 @@ struct LoginView: View {
}
.padding(.horizontal, 28)
.tint(AppColors.tertiary)
.offset(y: buttonVisible ? 0 : 140)
.opacity(buttonVisible ? 1.0 : 0.0)
HStack(spacing: 6) {
Text("Não tem conta ainda?")
@@ -81,11 +92,67 @@ struct LoginView: View {
.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() {
var transaction = Transaction()
transaction.disablesAnimations = true
withTransaction(transaction) {
heroVisible = false
textVisible = false
buttonVisible = false
}
}
@MainActor
private func showFinalStateWithoutAnimation() {
var transaction = Transaction()
transaction.disablesAnimations = true
withTransaction(transaction) {
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
}
}
}

View File

@@ -0,0 +1,47 @@
import SwiftUI
struct LaunchSplashView: View {
var shouldPulse: Bool = true
@Environment(\.colorScheme) var colorScheme
@State var isAnimating = false
var body: some View {
GeometryReader { geo in
let logoTopInset = max(0, (geo.size.height - 180) / 2 - 8)
ZStack(alignment: .top) {
(colorScheme == .dark ? Color.black : AppColors.backgroundLight)
.ignoresSafeArea()
VStack(spacing: 0) {
Spacer().frame(height: logoTopInset)
splashLogo
.scaledToFit()
.frame(width: 180, height: 180)
.scaleEffect(isAnimating ? 1.03 : 0.97)
.opacity(isAnimating ? 1.0 : 0.9)
.animation(.easeInOut(duration: 0.9).repeatForever(autoreverses: true), value: isAnimating)
Spacer()
}
}
}
.onAppear {
isAnimating = shouldPulse
}
.onChange(of: shouldPulse) { _, newValue in
isAnimating = newValue
}
}
@ViewBuilder var splashLogo: some View {
#if os(Android)
SwiftUI.Image("pedifoods")
.resizable()
#else
SwiftUI.Image("pedifoods")
.resizable()
#endif
}
}

View File

@@ -22,8 +22,8 @@ struct HomeView: View {
@State var stores: [StoreSummary] = []
private let specials: [SpecialOfferCardModel] = [
.init(id: "ddddd", title: "Get 50% OFF", subtitle: "For your first order", colors: [Color(hex: "#1E6B43"), Color(hex: "#58A56C")]),
.init(id: "eeeee", title: "Enjoy spicy day", subtitle: "Delivery in 20 min", colors: [Color(hex: "#F04B3E"), Color(hex: "#FF8C42")])
// .init(id: "ddddd", title: "Get 50% OFF", subtitle: "For your first order", colors: [Color(hex: "#1E6B43"), Color(hex: "#58A56C")]),
// .init(id: "eeeee", title: "Enjoy spicy day", subtitle: "Delivery in 20 min", colors: [Color(hex: "#F04B3E"), Color(hex: "#FF8C42")])
]
private let headerExpandedHeight: CGFloat = 240
@@ -123,7 +123,7 @@ struct HomeView: View {
}
}
section(title: "#SpecialForYou") {
section(title: "#PediPromo") {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
ForEach(specials) { item in
@@ -135,7 +135,7 @@ struct HomeView: View {
}
}
section(title: "Near you") {
section(title: "Pertinho de você") {
if isLoadingStores {
HStack {
ProgressView()
@@ -226,7 +226,7 @@ struct HomeView: View {
.buttonStyle(.plain)
VStack(alignment: .center, spacing: 4) {
Text("DELIVERY LOCATION")
Text("ENTREGAR EM:")
.font(AppTypography.overline)
.tracking(AppTypography.captionLetterSpacing)
.foregroundStyle(AppColors.brandSoft)

View File

@@ -60,12 +60,12 @@ struct ProfileView: View {
// }
// .buttonStyle(.plain)
NavigationLink {
Text("Ajuda")
} label: {
ProfileMenuRow(icon: "gearshape.fill", title: "Configurações")
}
.buttonStyle(.plain)
// NavigationLink {
// Text("Ajuda")
// } label: {
// ProfileMenuRow(icon: "gearshape.fill", title: "Configurações")
// }
// .buttonStyle(.plain)
}
.padding(.horizontal, 20)
@@ -82,7 +82,7 @@ struct ProfileView: View {
.padding(.top, 10)
.padding(.horizontal, 20)
Text("Versão 4.20.1")
Text("Versão 1.0b")
.font(.system(size: 16, weight: .medium))
.foregroundStyle(AppColors.textMuted)
.padding(.bottom, 12)