feat: introduce a new pastel yellow theme by adding dedicated color assets and updating various UI components to utilize them.

This commit is contained in:
Ho Ngoc Hai
2026-01-17 01:16:34 +07:00
parent 070d1e94b3
commit 90e96b57ac
29 changed files with 894 additions and 272 deletions

View File

@@ -1,6 +1,33 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.243",
"green" : "0.729",
"red" : "0.957"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.180",
"green" : "0.780",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.945",
"green" : "0.988",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.110",
"green" : "0.110",
"red" : "0.110"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.243",
"green" : "0.729",
"red" : "0.957"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.180",
"green" : "0.780",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.690",
"green" : "0.875",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.400",
"green" : "0.851",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.725",
"green" : "0.855",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.600",
"green" : "0.780",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.170",
"green" : "0.170",
"red" : "0.170"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.620",
"green" : "0.580",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.580",
"green" : "0.540",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.725",
"green" : "0.855",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.600",
"green" : "0.780",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.690",
"green" : "0.875",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.400",
"green" : "0.851",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "0.831",
"red" : "0.706"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "0.860",
"red" : "0.750"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.541",
"green" : "0.784",
"red" : "0.478"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.600",
"green" : "0.820",
"red" : "0.540"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.145",
"green" : "0.125",
"red" : "0.118"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.961",
"green" : "0.961",
"red" : "0.961"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.459",
"green" : "0.420",
"red" : "0.400"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.700",
"green" : "0.700",
"red" : "0.700"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.380",
"green" : "0.780",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.320",
"green" : "0.800",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -116,12 +116,26 @@ struct ContentView: View {
/// View loading khi đang kim tra auth
private var loadingView: some View {
ZStack {
Color(red: 0.05, green: 0.05, blue: 0.12)
Color.appBackground
.ignoresSafeArea()
ProgressView()
.scaleEffect(1.5)
.tint(.white)
VStack(spacing: 16) {
// App logo
ZStack {
Circle()
.fill(AppTheme.primaryGradient)
.frame(width: 80, height: 80)
Image(systemName: "location.fill")
.font(.system(size: 32))
.foregroundStyle(Color.textPrimary)
}
.shadow(color: AppTheme.cardShadow, radius: 20, x: 0, y: 10)
ProgressView()
.scaleEffect(1.2)
.tint(Color.brandAccent)
}
}
}
@@ -173,7 +187,7 @@ struct ContentView: View {
}
.tag(Tab.profile)
}
.tint(.accentColor)
.tint(Color.brandAccent)
}
}

View File

@@ -159,22 +159,63 @@ enum DesignSystem {
// MARK: - Colors
// Màu sc
/// App color palette
/// Bng màu ng dng
extension Color {
/// Primary brand color
/// Màu chính thương hiu
static let brandPrimary = Color("BrandPrimary", bundle: nil)
// Note: Color assets are auto-generated by Xcode 16+ from Assets.xcassets
// Các color assets đưc Xcode 16+ t đng generate t Assets.xcassets
//
// Available colors / Màu có sn:
// - .brandPrimary - Pastel Yellow (#FFDFB0)
// - .brandSecondary - Soft Peach (#FFDAB9)
// - .brandAccent - Warm Gold (#F4BA3E)
// - .appBackground - Warm White (#FFFCF1)
// - .cardBackground - Pure White (#FFFFFF)
// - .textPrimary - Dark Brown (#1E2025)
// - .textSecondary - Medium Gray (#666B75)
// - .success - Pastel Green (#7AC88A)
// - .warning - Soft Orange (#FFC761)
// - .error - Pastel Red (#FF949E)
// - .info - Pastel Blue (#B4D4FF)
// - .gradientStart - Light Yellow (#FFF5D6)
// - .gradientEnd - Soft Peach (#FFE8D6)
/// Secondary brand color
/// Màu ph thương hiu
static let brandSecondary = Color("BrandSecondary", bundle: nil)
// MARK: - Theme
// Theme ng dng
/// Background color
/// Màu nn
static let appBackground = Color("AppBackground", bundle: nil)
/// App theme configuration
/// Cu hình theme ng dng
enum AppTheme {
/// Card background color
/// Màu nn card
static let cardBackground = Color("CardBackground", bundle: nil)
/// Primary gradient - Pastel Yellow to Peach
/// Gradient chính - Vàng pastel sang Đào
static let primaryGradient = LinearGradient(
colors: [Color.gradientStart, Color.gradientEnd],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
/// Warm gradient - Yellow to Orange accent
/// Gradient m - Vàng sang cam nhn
static let warmGradient = LinearGradient(
colors: [Color.brandPrimary, Color.brandAccent],
startPoint: .top,
endPoint: .bottom
)
/// Soft gradient - Light yellow with transparency
/// Gradient nh - Vàng nht vi đ trong sut
static let softGradient = LinearGradient(
colors: [
Color.brandPrimary.opacity(0.3),
Color.brandSecondary.opacity(0.2)
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
/// Card shadow color
/// Màu đ bóng card
static let cardShadow = Color.brandAccent.opacity(0.15)
/// Button shadow color
/// Màu đ bóng nút
static let buttonShadow = Color.brandAccent.opacity(0.3)
}

View File

@@ -26,16 +26,26 @@ struct AuthContainerView: View {
var body: some View {
ZStack {
// Background gradient
// Gradient nn
LinearGradient(
colors: [
Color(red: 0.05, green: 0.05, blue: 0.12),
Color(red: 0.1, green: 0.08, blue: 0.18)
],
startPoint: .top,
endPoint: .bottom
)
// Background - Pastel Yellow Theme
// Nn - Theme Pastel Vàng
Color.appBackground
.ignoresSafeArea()
// Decorative gradient circles
// Các vòng tròn gradient trang trí
GeometryReader { geometry in
Circle()
.fill(AppTheme.softGradient)
.frame(width: 300, height: 300)
.blur(radius: 60)
.offset(x: -100, y: -50)
Circle()
.fill(Color.brandSecondary.opacity(0.2))
.frame(width: 200, height: 200)
.blur(radius: 40)
.offset(x: geometry.size.width - 100, y: geometry.size.height - 200)
}
.ignoresSafeArea()
// Content

View File

@@ -2,8 +2,8 @@
// ForgotPasswordView.swift
// AppClientBaseSwift
//
// Forgot password screen view
// View màn hình quên mt khu
// Forgot password screen view - Pastel Yellow Theme
// View màn hình quên mt khu - Theme Pastel Vàng
//
import SwiftUI
@@ -91,7 +91,7 @@ struct ForgotPasswordView: View {
Text("Quay lại")
}
.font(.subheadline)
.foregroundStyle(.white.opacity(0.8))
.foregroundStyle(Color.textSecondary)
}
Spacer()
}
@@ -102,11 +102,11 @@ struct ForgotPasswordView: View {
Text("Quên mật khẩu?")
.font(.title)
.fontWeight(.bold)
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
Text("Nhập email để nhận link đặt lại")
.font(.subheadline)
.foregroundStyle(.white.opacity(0.6))
.foregroundStyle(Color.textSecondary)
}
}
}
@@ -120,7 +120,7 @@ struct ForgotPasswordView: View {
.fill(
RadialGradient(
colors: [
Color.blue.opacity(0.2),
Color.brandPrimary.opacity(0.3),
Color.clear
],
center: .center,
@@ -133,23 +133,14 @@ struct ForgotPasswordView: View {
// Icon
ZStack {
Circle()
.fill(
LinearGradient(
colors: [
Color(red: 0.2, green: 0.6, blue: 1.0),
Color(red: 0.4, green: 0.3, blue: 0.9)
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.fill(AppTheme.primaryGradient)
.frame(width: 100, height: 100)
Image(systemName: "envelope.open.fill")
.font(.system(size: 40))
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
}
.shadow(color: .blue.opacity(0.4), radius: 15, x: 0, y: 8)
.shadow(color: AppTheme.cardShadow, radius: 20, x: 0, y: 10)
}
}
@@ -170,7 +161,7 @@ struct ForgotPasswordView: View {
// Help text
Text("Chúng tôi sẽ gửi link đặt lại mật khẩu đến email của bạn.")
.font(.caption)
.foregroundStyle(.white.opacity(0.5))
.foregroundStyle(Color.textSecondary)
.multilineTextAlignment(.center)
}
}
@@ -186,20 +177,29 @@ struct ForgotPasswordView: View {
Image(systemName: "paperplane.fill")
}
.font(.headline)
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
.frame(maxWidth: .infinity)
.padding(.vertical, DesignSystem.spacingMD)
.background(
LinearGradient(
colors: viewModel.isForgotPasswordValid
? [Color(red: 0.2, green: 0.6, blue: 1.0), Color(red: 0.4, green: 0.3, blue: 0.9)]
: [Color.gray, Color.gray.opacity(0.8)],
startPoint: .leading,
endPoint: .trailing
)
Group {
if viewModel.isForgotPasswordValid {
AppTheme.warmGradient
} else {
LinearGradient(
colors: [Color.gray.opacity(0.3), Color.gray.opacity(0.2)],
startPoint: .leading,
endPoint: .trailing
)
}
}
)
.cornerRadius(DesignSystem.cornerRadiusMD)
.shadow(color: viewModel.isForgotPasswordValid ? .blue.opacity(0.4) : .clear, radius: 10, x: 0, y: 5)
.shadow(
color: viewModel.isForgotPasswordValid ? AppTheme.buttonShadow : .clear,
radius: 15,
x: 0,
y: 8
)
}
.disabled(!viewModel.isForgotPasswordValid)
}
@@ -215,7 +215,7 @@ struct ForgotPasswordView: View {
Text("Quay lại đăng nhập")
}
.font(.subheadline)
.foregroundStyle(.blue)
.foregroundStyle(Color.brandAccent)
}
}

View File

@@ -2,8 +2,8 @@
// LoginView.swift
// AppClientBaseSwift
//
// Login screen view
// View màn hình đăng nhp
// Login screen view - Pastel Yellow Theme
// View màn hình đăng nhp - Theme Pastel Vàng
//
import SwiftUI
@@ -83,34 +83,25 @@ struct LoginView: View {
// Logo
ZStack {
Circle()
.fill(
LinearGradient(
colors: [
Color(red: 0.2, green: 0.6, blue: 1.0),
Color(red: 0.4, green: 0.3, blue: 0.9)
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.frame(width: 70, height: 70)
.fill(AppTheme.primaryGradient)
.frame(width: 80, height: 80)
Image(systemName: "location.fill")
.font(.system(size: 30))
.foregroundStyle(.white)
.font(.system(size: 32))
.foregroundStyle(Color.textPrimary)
}
.shadow(color: .blue.opacity(0.4), radius: 15, x: 0, y: 8)
.shadow(color: AppTheme.cardShadow, radius: 20, x: 0, y: 10)
// Title
VStack(spacing: DesignSystem.spacingXS) {
Text("Chào mừng trở lại!")
.font(.title)
.fontWeight(.bold)
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
Text("Đăng nhập để tiếp tục")
.font(.subheadline)
.foregroundStyle(.white.opacity(0.6))
.foregroundStyle(Color.textSecondary)
}
}
}
@@ -148,7 +139,7 @@ struct LoginView: View {
} label: {
Text("Quên mật khẩu?")
.font(.subheadline)
.foregroundStyle(.blue)
.foregroundStyle(Color.brandAccent)
}
}
}
@@ -165,20 +156,29 @@ struct LoginView: View {
Image(systemName: "arrow.right")
}
.font(.headline)
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
.frame(maxWidth: .infinity)
.padding(.vertical, DesignSystem.spacingMD)
.background(
LinearGradient(
colors: viewModel.isLoginValid
? [Color(red: 0.2, green: 0.6, blue: 1.0), Color(red: 0.4, green: 0.3, blue: 0.9)]
: [Color.gray, Color.gray.opacity(0.8)],
startPoint: .leading,
endPoint: .trailing
)
Group {
if viewModel.isLoginValid {
AppTheme.warmGradient
} else {
LinearGradient(
colors: [Color.gray.opacity(0.3), Color.gray.opacity(0.2)],
startPoint: .leading,
endPoint: .trailing
)
}
}
)
.cornerRadius(DesignSystem.cornerRadiusMD)
.shadow(color: viewModel.isLoginValid ? .blue.opacity(0.4) : .clear, radius: 10, x: 0, y: 5)
.shadow(
color: viewModel.isLoginValid ? AppTheme.buttonShadow : .clear,
radius: 15,
x: 0,
y: 8
)
}
.disabled(!viewModel.isLoginValid)
}
@@ -188,15 +188,15 @@ struct LoginView: View {
private var dividerSection: some View {
HStack(spacing: DesignSystem.spacingMD) {
Rectangle()
.fill(Color.white.opacity(0.2))
.fill(Color.textSecondary.opacity(0.3))
.frame(height: 1)
Text("hoặc")
.font(.subheadline)
.foregroundStyle(.white.opacity(0.5))
.foregroundStyle(Color.textSecondary)
Rectangle()
.fill(Color.white.opacity(0.2))
.fill(Color.textSecondary.opacity(0.3))
.frame(height: 1)
}
}
@@ -205,7 +205,7 @@ struct LoginView: View {
/// Các nút đăng nhp mng xã hi
private var socialButtons: some View {
HStack(spacing: DesignSystem.spacingMD) {
SocialLoginButton(icon: "apple.logo", color: .white) {
SocialLoginButton(icon: "apple.logo", color: .black) {
// Apple login
}
@@ -224,14 +224,14 @@ struct LoginView: View {
private var registerSection: some View {
HStack(spacing: DesignSystem.spacingXS) {
Text("Chưa có tài khoản?")
.foregroundStyle(.white.opacity(0.6))
.foregroundStyle(Color.textSecondary)
Button {
viewModel.navigateTo(.register)
} label: {
Text("Đăng ký ngay")
.fontWeight(.semibold)
.foregroundStyle(.blue)
.foregroundStyle(Color.brandAccent)
}
}
.font(.subheadline)
@@ -252,8 +252,8 @@ struct LoginView: View {
// MARK: - Auth Text Field
// Text Field Auth
/// Custom text field for auth forms
/// Text field tùy chnh cho form auth
/// Custom text field for auth forms - Pastel Theme
/// Text field tùy chnh cho form auth - Theme Pastel
struct AuthTextField: View {
let icon: String
let placeholder: String
@@ -264,31 +264,32 @@ struct AuthTextField: View {
HStack(spacing: DesignSystem.spacingMD) {
Image(systemName: icon)
.font(.system(size: 18))
.foregroundStyle(.white.opacity(0.6))
.foregroundStyle(Color.brandAccent)
.frame(width: 24)
TextField("", text: $text, prompt: Text(placeholder).foregroundStyle(.white.opacity(0.4)))
.foregroundStyle(.white)
TextField("", text: $text, prompt: Text(placeholder).foregroundStyle(Color.textSecondary.opacity(0.6)))
.foregroundStyle(Color.textPrimary)
.keyboardType(keyboardType)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
.padding(.horizontal, DesignSystem.spacingMD)
.padding(.vertical, DesignSystem.spacingMD)
.background(Color.white.opacity(0.1))
.background(Color.cardBackground)
.cornerRadius(DesignSystem.cornerRadiusMD)
.overlay(
RoundedRectangle(cornerRadius: DesignSystem.cornerRadiusMD)
.stroke(Color.white.opacity(0.2), lineWidth: 1)
.stroke(Color.brandPrimary.opacity(0.5), lineWidth: 1.5)
)
.shadow(color: AppTheme.cardShadow, radius: 5, x: 0, y: 2)
}
}
// MARK: - Auth Secure Field
// Secure Field Auth
/// Custom secure field for auth forms
/// Secure field tùy chnh cho form auth
/// Custom secure field for auth forms - Pastel Theme
/// Secure field tùy chnh cho form auth - Theme Pastel
struct AuthSecureField: View {
let icon: String
let placeholder: String
@@ -299,17 +300,17 @@ struct AuthSecureField: View {
HStack(spacing: DesignSystem.spacingMD) {
Image(systemName: icon)
.font(.system(size: 18))
.foregroundStyle(.white.opacity(0.6))
.foregroundStyle(Color.brandAccent)
.frame(width: 24)
Group {
if isVisible {
TextField("", text: $text, prompt: Text(placeholder).foregroundStyle(.white.opacity(0.4)))
TextField("", text: $text, prompt: Text(placeholder).foregroundStyle(Color.textSecondary.opacity(0.6)))
} else {
SecureField("", text: $text, prompt: Text(placeholder).foregroundStyle(.white.opacity(0.4)))
SecureField("", text: $text, prompt: Text(placeholder).foregroundStyle(Color.textSecondary.opacity(0.6)))
}
}
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
@@ -317,25 +318,26 @@ struct AuthSecureField: View {
isVisible.toggle()
} label: {
Image(systemName: isVisible ? "eye.slash.fill" : "eye.fill")
.foregroundStyle(.white.opacity(0.6))
.foregroundStyle(Color.textSecondary)
}
}
.padding(.horizontal, DesignSystem.spacingMD)
.padding(.vertical, DesignSystem.spacingMD)
.background(Color.white.opacity(0.1))
.background(Color.cardBackground)
.cornerRadius(DesignSystem.cornerRadiusMD)
.overlay(
RoundedRectangle(cornerRadius: DesignSystem.cornerRadiusMD)
.stroke(Color.white.opacity(0.2), lineWidth: 1)
.stroke(Color.brandPrimary.opacity(0.5), lineWidth: 1.5)
)
.shadow(color: AppTheme.cardShadow, radius: 5, x: 0, y: 2)
}
}
// MARK: - Social Login Button
// Nút đăng nhp mng xã hi
/// Social login button component
/// Component nút đăng nhp mng xã hi
/// Social login button component - Pastel Theme
/// Component nút đăng nhp mng xã hi - Theme Pastel
struct SocialLoginButton: View {
let icon: String
let color: Color
@@ -347,12 +349,13 @@ struct SocialLoginButton: View {
.font(.system(size: 24))
.foregroundStyle(color)
.frame(width: 56, height: 56)
.background(Color.white.opacity(0.1))
.background(Color.cardBackground)
.cornerRadius(DesignSystem.cornerRadiusMD)
.overlay(
RoundedRectangle(cornerRadius: DesignSystem.cornerRadiusMD)
.stroke(Color.white.opacity(0.2), lineWidth: 1)
.stroke(Color.brandPrimary.opacity(0.3), lineWidth: 1)
)
.shadow(color: AppTheme.cardShadow, radius: 5, x: 0, y: 2)
}
}
}

View File

@@ -2,8 +2,8 @@
// RegisterView.swift
// AppClientBaseSwift
//
// Registration screen view
// View màn hình đăng ký
// Registration screen view - Pastel Yellow Theme
// View màn hình đăng ký - Theme Pastel Vàng
//
import SwiftUI
@@ -84,7 +84,7 @@ struct RegisterView: View {
Text("Quay lại")
}
.font(.subheadline)
.foregroundStyle(.white.opacity(0.8))
.foregroundStyle(Color.textSecondary)
}
Spacer()
}
@@ -95,11 +95,11 @@ struct RegisterView: View {
Text("Tạo tài khoản")
.font(.title)
.fontWeight(.bold)
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
Text("Điền thông tin để đăng ký")
.font(.subheadline)
.foregroundStyle(.white.opacity(0.6))
.foregroundStyle(Color.textSecondary)
}
}
}
@@ -147,7 +147,7 @@ struct RegisterView: View {
RoundedRectangle(cornerRadius: 2)
.fill(index < viewModel.passwordStrength
? viewModel.passwordStrengthColor
: Color.white.opacity(0.2))
: Color.textSecondary.opacity(0.2))
.frame(height: 4)
}
}
@@ -180,8 +180,8 @@ struct RegisterView: View {
}
.font(.caption)
.foregroundStyle(viewModel.registerPassword == viewModel.registerConfirmPassword
? .green
: .red)
? Color.success
: Color.error)
}
// Terms checkbox
@@ -198,19 +198,19 @@ struct RegisterView: View {
} label: {
HStack(alignment: .top, spacing: DesignSystem.spacingSM) {
Image(systemName: viewModel.agreedToTerms ? "checkmark.square.fill" : "square")
.foregroundStyle(viewModel.agreedToTerms ? .blue : .white.opacity(0.6))
.foregroundStyle(viewModel.agreedToTerms ? Color.brandAccent : Color.textSecondary)
Text("Tôi đồng ý với ")
.foregroundStyle(.white.opacity(0.6))
.foregroundStyle(Color.textSecondary)
+
Text("Điều khoản sử dụng")
.foregroundStyle(.blue)
.foregroundStyle(Color.brandAccent)
+
Text("")
.foregroundStyle(.white.opacity(0.6))
.foregroundStyle(Color.textSecondary)
+
Text("Chính sách bảo mật")
.foregroundStyle(.blue)
.foregroundStyle(Color.brandAccent)
}
.font(.subheadline)
.multilineTextAlignment(.leading)
@@ -228,20 +228,29 @@ struct RegisterView: View {
Image(systemName: "arrow.right")
}
.font(.headline)
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
.frame(maxWidth: .infinity)
.padding(.vertical, DesignSystem.spacingMD)
.background(
LinearGradient(
colors: viewModel.isRegisterValid
? [Color(red: 0.2, green: 0.6, blue: 1.0), Color(red: 0.4, green: 0.3, blue: 0.9)]
: [Color.gray, Color.gray.opacity(0.8)],
startPoint: .leading,
endPoint: .trailing
)
Group {
if viewModel.isRegisterValid {
AppTheme.warmGradient
} else {
LinearGradient(
colors: [Color.gray.opacity(0.3), Color.gray.opacity(0.2)],
startPoint: .leading,
endPoint: .trailing
)
}
}
)
.cornerRadius(DesignSystem.cornerRadiusMD)
.shadow(color: viewModel.isRegisterValid ? .blue.opacity(0.4) : .clear, radius: 10, x: 0, y: 5)
.shadow(
color: viewModel.isRegisterValid ? AppTheme.buttonShadow : .clear,
radius: 15,
x: 0,
y: 8
)
}
.disabled(!viewModel.isRegisterValid)
}
@@ -251,14 +260,14 @@ struct RegisterView: View {
private var loginSection: some View {
HStack(spacing: DesignSystem.spacingXS) {
Text("Đã có tài khoản?")
.foregroundStyle(.white.opacity(0.6))
.foregroundStyle(Color.textSecondary)
Button {
viewModel.navigateTo(.login)
} label: {
Text("Đăng nhập")
.fontWeight(.semibold)
.foregroundStyle(.blue)
.foregroundStyle(Color.brandAccent)
}
}
.font(.subheadline)

View File

@@ -2,8 +2,8 @@
// ActivityFeed.swift
// AppClientBaseSwift
//
// Recent activity/transaction feed
// Feed hot đng/giao dch gn đây
// Recent activity/transaction feed - Pastel Yellow Theme
// Feed hot đng/giao dch gn đây - Theme Pastel Vàng
//
import SwiftUI
@@ -34,7 +34,7 @@ struct ActivityItem: Identifiable {
subtitle: "Quận 1 → Quận 7",
amount: -45000,
icon: "car.fill",
color: .green,
color: Color.success,
time: "10:30"
),
ActivityItem(
@@ -42,7 +42,7 @@ struct ActivityItem: Identifiable {
subtitle: "Từ Nguyễn Văn A",
amount: 200000,
icon: "arrow.down.circle.fill",
color: .blue,
color: Color.info,
time: "Hôm qua"
),
ActivityItem(
@@ -50,7 +50,7 @@ struct ActivityItem: Identifiable {
subtitle: "EVN HCM",
amount: -350000,
icon: "bolt.fill",
color: .yellow,
color: Color.brandAccent,
time: "20/01"
),
ActivityItem(
@@ -58,7 +58,7 @@ struct ActivityItem: Identifiable {
subtitle: "Khuyến mãi đặt xe",
amount: 15000,
icon: "gift.fill",
color: .pink,
color: Color.brandSecondary,
time: "19/01"
)
]
@@ -97,6 +97,7 @@ struct ActivityFeed: View {
Text("Hoạt động gần đây")
.font(.headline)
.fontWeight(.semibold)
.foregroundStyle(Color.textPrimary)
Spacer()
@@ -104,7 +105,7 @@ struct ActivityFeed: View {
// Show all activities
}
.font(.subheadline)
.foregroundStyle(.blue)
.foregroundStyle(Color.brandAccent)
}
// Activity list
@@ -122,9 +123,9 @@ struct ActivityFeed: View {
}
}
}
.background(Color.white)
.background(Color.cardBackground)
.cornerRadius(16)
.shadow(color: .black.opacity(0.05), radius: 10, x: 0, y: 5)
.shadow(color: AppTheme.cardShadow, radius: 10, x: 0, y: 5)
}
}
}
@@ -156,11 +157,11 @@ struct ActivityRow: View {
Text(activity.title)
.font(.subheadline)
.fontWeight(.medium)
.foregroundStyle(.primary)
.foregroundStyle(Color.textPrimary)
Text(activity.subtitle)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(Color.textSecondary)
}
Spacer()
@@ -170,11 +171,11 @@ struct ActivityRow: View {
Text(activity.isPositive ? "+\(String.formatVND(activity.amount))" : String.formatVND(activity.amount))
.font(.subheadline)
.fontWeight(.semibold)
.foregroundStyle(activity.isPositive ? .green : .primary)
.foregroundStyle(activity.isPositive ? Color.success : Color.textPrimary)
Text(activity.time)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(Color.textSecondary)
}
}
.padding(.horizontal, 16)
@@ -186,7 +187,7 @@ struct ActivityRow: View {
#Preview {
ZStack {
Color.gray.opacity(0.1).ignoresSafeArea()
Color.appBackground.ignoresSafeArea()
ActivityFeed()
.padding()
}

View File

@@ -2,8 +2,8 @@
// PromoCarousel.swift
// AppClientBaseSwift
//
// Promotional banners carousel
// Carousel banners khuyến mãi
// Promotional banners carousel - Pastel Yellow Theme
// Carousel banners khuyến mãi - Theme Pastel Vàng
//
import SwiftUI
@@ -24,19 +24,19 @@ struct PromoItem: Identifiable {
PromoItem(
title: "Giảm 50% Đặt xe",
subtitle: "Áp dụng đến 31/01",
gradientColors: [.orange, .red],
gradientColors: [Color.brandPrimary, Color.brandAccent],
icon: "car.fill"
),
PromoItem(
title: "Freeship Đồ ăn",
subtitle: "Đơn từ 50K",
gradientColors: [.green, .mint],
gradientColors: [Color.success, Color.success.opacity(0.7)],
icon: "takeoutbag.and.cup.and.straw.fill"
),
PromoItem(
title: "Hoàn tiền 20%",
subtitle: "Thanh toán hóa đơn",
gradientColors: [.blue, .purple],
gradientColors: [Color.info, Color.brandSecondary],
icon: "creditcard.fill"
)
]
@@ -88,7 +88,7 @@ struct PromoCarousel: View {
HStack(spacing: 6) {
ForEach(0..<items.count, id: \.self) { index in
Circle()
.fill(index == currentIndex ? Color.accentColor : Color.gray.opacity(0.3))
.fill(index == currentIndex ? Color.brandAccent : Color.textSecondary.opacity(0.3))
.frame(width: index == currentIndex ? 8 : 6, height: index == currentIndex ? 8 : 6)
.animation(.easeInOut(duration: 0.2), value: currentIndex)
}
@@ -138,11 +138,11 @@ struct PromoCard: View {
Text(item.title)
.font(.headline)
.fontWeight(.bold)
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
Text(item.subtitle)
.font(.subheadline)
.foregroundStyle(.white.opacity(0.8))
.foregroundStyle(Color.textPrimary.opacity(0.7))
Spacer()
@@ -150,10 +150,10 @@ struct PromoCard: View {
Text("Xem ngay →")
.font(.caption)
.fontWeight(.semibold)
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(.white.opacity(0.2))
.background(Color.white.opacity(0.5))
.cornerRadius(12)
}
.padding(20)
@@ -163,7 +163,7 @@ struct PromoCard: View {
// Icon
Image(systemName: item.icon)
.font(.system(size: 50))
.foregroundStyle(.white.opacity(0.3))
.foregroundStyle(Color.textPrimary.opacity(0.2))
.padding(.trailing, 20)
}
.frame(maxWidth: .infinity)
@@ -175,6 +175,7 @@ struct PromoCard: View {
)
)
.cornerRadius(16)
.shadow(color: AppTheme.cardShadow, radius: 10, x: 0, y: 5)
.padding(.horizontal, 4)
}
}
@@ -182,6 +183,9 @@ struct PromoCard: View {
// MARK: - Preview
#Preview {
PromoCarousel()
.padding()
ZStack {
Color.appBackground.ignoresSafeArea()
PromoCarousel()
.padding()
}
}

View File

@@ -2,8 +2,8 @@
// ServiceGrid.swift
// AppClientBaseSwift
//
// Services grid with icons and labels
// Grid services vi icons và labels
// Services grid with icons and labels - Pastel Yellow Theme
// Grid services vi icons và labels - Theme Pastel Vàng
//
import SwiftUI
@@ -21,14 +21,14 @@ struct ServiceItem: Identifiable {
let action: String
static let defaultServices: [ServiceItem] = [
ServiceItem(icon: "car.fill", title: "Đặt xe", color: .orange, action: "booking"),
ServiceItem(icon: "takeoutbag.and.cup.and.straw.fill", title: "Đồ ăn", color: .red, action: "food"),
ServiceItem(icon: "creditcard.fill", title: "Thanh toán", color: .blue, action: "payment"),
ServiceItem(icon: "arrow.left.arrow.right", title: "Chuyển tiền", color: .purple, action: "transfer"),
ServiceItem(icon: "tag.fill", title: "Khuyến mãi", color: .pink, action: "promo"),
ServiceItem(icon: "plus.circle.fill", title: "Nạp tiền", color: .green, action: "topup"),
ServiceItem(icon: "ticket.fill", title: "Đặt vé", color: .orange, action: "tickets"),
ServiceItem(icon: "ellipsis", title: "Thêm", color: .gray, action: "more")
ServiceItem(icon: "car.fill", title: "Đặt xe", color: Color.brandAccent, action: "booking"),
ServiceItem(icon: "takeoutbag.and.cup.and.straw.fill", title: "Đồ ăn", color: Color.error, action: "food"),
ServiceItem(icon: "creditcard.fill", title: "Thanh toán", color: Color.info, action: "payment"),
ServiceItem(icon: "arrow.left.arrow.right", title: "Chuyển tiền", color: Color.brandSecondary, action: "transfer"),
ServiceItem(icon: "tag.fill", title: "Khuyến mãi", color: Color.warning, action: "promo"),
ServiceItem(icon: "plus.circle.fill", title: "Nạp tiền", color: Color.success, action: "topup"),
ServiceItem(icon: "ticket.fill", title: "Đặt vé", color: Color.brandPrimary, action: "tickets"),
ServiceItem(icon: "ellipsis", title: "Thêm", color: Color.textSecondary, action: "more")
]
}
@@ -105,7 +105,7 @@ struct ServiceGridItem: View {
Text(service.title)
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.primary)
.foregroundStyle(Color.textPrimary)
.lineLimit(1)
}
}
@@ -114,6 +114,9 @@ struct ServiceGridItem: View {
// MARK: - Preview
#Preview {
ServiceGrid()
.padding()
ZStack {
Color.appBackground.ignoresSafeArea()
ServiceGrid()
.padding()
}
}

View File

@@ -2,8 +2,8 @@
// WalletCard.swift
// AppClientBaseSwift
//
// Wallet card with balance and quick actions
// Card ví vi s dư và quick actions
// Wallet card with balance and quick actions - Pastel Yellow Theme
// Card ví vi s dư và quick actions - Theme Pastel Vàng
//
import SwiftUI
@@ -59,17 +59,24 @@ struct WalletCard: View {
}
.padding(20)
.background(
LinearGradient(
colors: [
Color(red: 0.4, green: 0.5, blue: 0.92),
Color(red: 0.47, green: 0.3, blue: 0.64)
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
ZStack {
// Main gradient
AppTheme.warmGradient
// Decorative circles
Circle()
.fill(Color.white.opacity(0.1))
.frame(width: 150, height: 150)
.offset(x: -80, y: -60)
Circle()
.fill(Color.white.opacity(0.08))
.frame(width: 100, height: 100)
.offset(x: 120, y: 40)
}
)
.cornerRadius(20)
.shadow(color: Color(red: 0.4, green: 0.3, blue: 0.7).opacity(0.4), radius: 15, x: 0, y: 10)
.shadow(color: AppTheme.buttonShadow, radius: 20, x: 0, y: 12)
}
// MARK: - Subviews
@@ -81,17 +88,17 @@ struct WalletCard: View {
VStack(alignment: .leading, spacing: 6) {
Text("Số dư khả dụng")
.font(.subheadline)
.foregroundStyle(.white.opacity(0.8))
.foregroundStyle(Color.textPrimary.opacity(0.7))
HStack(spacing: 8) {
if isBalanceHidden {
Text("••••••••")
.font(.system(size: 28, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
} else {
Text(String.formatVND(balance))
.font(.system(size: 28, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
}
Button {
@@ -101,7 +108,7 @@ struct WalletCard: View {
} label: {
Image(systemName: isBalanceHidden ? "eye.slash.fill" : "eye.fill")
.font(.subheadline)
.foregroundStyle(.white.opacity(0.8))
.foregroundStyle(Color.textPrimary.opacity(0.6))
}
}
}
@@ -113,16 +120,16 @@ struct WalletCard: View {
VStack(alignment: .trailing, spacing: 4) {
HStack(spacing: 4) {
Image(systemName: "star.circle.fill")
.foregroundStyle(.yellow)
.foregroundStyle(Color.brandAccent)
Text("1,250")
.fontWeight(.semibold)
}
.font(.subheadline)
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
Text("điểm thưởng")
.font(.caption)
.foregroundStyle(.white.opacity(0.7))
.foregroundStyle(Color.textPrimary.opacity(0.6))
}
}
}
@@ -138,18 +145,18 @@ struct WalletCard: View {
VStack(spacing: 8) {
ZStack {
Circle()
.fill(.white.opacity(0.2))
.fill(Color.white.opacity(0.4))
.frame(width: 48, height: 48)
Image(systemName: action.icon)
.font(.title2)
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
}
Text(action.rawValue)
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
}
}
.frame(maxWidth: .infinity)
@@ -162,7 +169,7 @@ struct WalletCard: View {
#Preview {
ZStack {
Color.gray.opacity(0.1).ignoresSafeArea()
Color.appBackground.ignoresSafeArea()
WalletCard(balance: 1_250_000)
.padding()
}

View File

@@ -2,8 +2,8 @@
// HomeView.swift
// AppClientBaseSwift
//
// Super App style home screen
// Màn hình home phong cách Super App
// Super App style home screen - Pastel Yellow Theme
// Màn hình home phong cách Super App - Theme Pastel Vàng
//
import SwiftUI
@@ -48,6 +48,7 @@ struct HomeView: View {
Text("Dịch vụ")
.font(.headline)
.fontWeight(.semibold)
.foregroundStyle(Color.textPrimary)
.padding(.horizontal, 20)
ServiceGrid { service in
@@ -63,6 +64,7 @@ struct HomeView: View {
Text("Ưu đãi hấp dẫn")
.font(.headline)
.fontWeight(.semibold)
.foregroundStyle(Color.textPrimary)
Spacer()
@@ -70,7 +72,7 @@ struct HomeView: View {
// Show all promos
}
.font(.subheadline)
.foregroundStyle(.blue)
.foregroundStyle(Color.brandAccent)
}
.padding(.horizontal, 20)
@@ -90,7 +92,7 @@ struct HomeView: View {
}
.padding(.top, 10)
}
.background(Color(red: 0.96, green: 0.97, blue: 0.98))
.background(Color.appBackground)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
@@ -101,7 +103,7 @@ struct HomeView: View {
} label: {
Image(systemName: "qrcode.viewfinder")
.font(.title3)
.foregroundStyle(.primary)
.foregroundStyle(Color.textPrimary)
}
// Notifications
@@ -110,7 +112,7 @@ struct HomeView: View {
} label: {
Image(systemName: "bell.badge")
.font(.title3)
.foregroundStyle(.primary)
.foregroundStyle(Color.brandAccent)
}
}
}
@@ -131,11 +133,12 @@ struct HomeView: View {
VStack(alignment: .leading, spacing: 4) {
Text(greetingText)
.font(.subheadline)
.foregroundStyle(.secondary)
.foregroundStyle(Color.textSecondary)
Text(authManager.currentUser?.name ?? "Người dùng")
.font(.title3)
.fontWeight(.bold)
.foregroundStyle(Color.textPrimary)
}
Spacer()
@@ -161,6 +164,7 @@ struct HomeView: View {
}
.frame(width: 50, height: 50)
.clipShape(Circle())
.shadow(color: AppTheme.cardShadow, radius: 5, x: 0, y: 2)
}
/// Avatar placeholder
@@ -168,18 +172,12 @@ struct HomeView: View {
private var avatarPlaceholder: some View {
ZStack {
Circle()
.fill(
LinearGradient(
colors: [.blue, .purple],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.fill(AppTheme.primaryGradient)
Text(authManager.currentUser?.initials ?? "U")
.font(.headline)
.fontWeight(.bold)
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
}
}
@@ -189,11 +187,11 @@ struct HomeView: View {
let hour = Calendar.current.component(.hour, from: Date())
switch hour {
case 5..<12:
return "Chào buổi sáng 👋"
return "Chào buổi sáng"
case 12..<18:
return "Chào buổi chiều ☀️"
return "Chào buổi chiều"
default:
return "Chào buổi tối 🌙"
return "Chào buổi tối"
}
}

View File

@@ -79,11 +79,11 @@ struct ProfileView: View {
HStack(spacing: 4) {
Image(systemName: "checkmark.seal.fill")
.font(.caption)
.foregroundStyle(.green)
.foregroundStyle(Color.success)
Text("profile_verified".localized)
.font(.caption)
.foregroundStyle(.green)
.foregroundStyle(Color.success)
}
}
}
@@ -124,18 +124,12 @@ struct ProfileView: View {
private var avatarPlaceholder: some View {
ZStack {
Circle()
.fill(
LinearGradient(
colors: [.blue, .purple],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.fill(AppTheme.primaryGradient)
Text(viewModel.user?.initials ?? "GU")
.font(.title2)
.fontWeight(.bold)
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
}
}
@@ -161,15 +155,15 @@ struct ProfileView: View {
Image(systemName: item.icon)
.font(.body)
.foregroundStyle(
item.action == .logout ? .red :
item.action == .apiDebugger ? .orange :
.accentColor
item.action == .logout ? Color.error :
item.action == .apiDebugger ? Color.warning :
Color.brandAccent
)
.frame(width: 24)
// Title
Text(item.title)
.foregroundStyle(item.action == .logout ? .red : .primary)
.foregroundStyle(item.action == .logout ? Color.error : Color.textPrimary)
Spacer()

View File

@@ -2,8 +2,8 @@
// WelcomeView.swift
// AppClientBaseSwift
//
// Onboarding/Welcome screen view
// View màn hình Onboarding/Welcome
// Onboarding/Welcome screen view - Pastel Yellow Theme
// View màn hình Onboarding/Welcome - Theme Pastel Vàng
//
import SwiftUI
@@ -48,25 +48,46 @@ struct WelcomeView: View {
// MARK: - Body
var body: some View {
VStack(spacing: 0) {
// Page content
// Ni dung trang
TabView(selection: $currentPage) {
ForEach(0..<pages.count, id: \.self) { index in
onboardingPage(index: index)
.tag(index)
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
.animation(.easeInOut, value: currentPage)
ZStack {
// Background
Color.appBackground
.ignoresSafeArea()
// Bottom section
// Phn dưi
bottomSection
.padding(.horizontal, DesignSystem.spacingLG)
.padding(.bottom, DesignSystem.spacingXL)
// Decorative circles
GeometryReader { geometry in
Circle()
.fill(AppTheme.softGradient)
.frame(width: 300, height: 300)
.blur(radius: 60)
.offset(x: -100, y: 100)
Circle()
.fill(Color.brandSecondary.opacity(0.2))
.frame(width: 200, height: 200)
.blur(radius: 40)
.offset(x: geometry.size.width - 80, y: geometry.size.height - 300)
}
.ignoresSafeArea()
VStack(spacing: 0) {
// Page content
// Ni dung trang
TabView(selection: $currentPage) {
ForEach(0..<pages.count, id: \.self) { index in
onboardingPage(index: index)
.tag(index)
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
.animation(.easeInOut, value: currentPage)
// Bottom section
// Phn dưi
bottomSection
.padding(.horizontal, DesignSystem.spacingLG)
.padding(.bottom, DesignSystem.spacingXL)
}
}
.ignoresSafeArea(edges: .top)
}
// MARK: - Subviews
@@ -81,20 +102,14 @@ struct WelcomeView: View {
// Icon
ZStack {
Circle()
.fill(
LinearGradient(
colors: [.blue.opacity(0.8), .purple.opacity(0.8)],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.fill(AppTheme.primaryGradient)
.frame(width: 140, height: 140)
Image(systemName: pages[index].icon)
.font(.system(size: 60))
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
}
.shadow(color: .blue.opacity(0.3), radius: 20, x: 0, y: 10)
.shadow(color: AppTheme.cardShadow, radius: 25, x: 0, y: 12)
// Text content
// Ni dung text
@@ -102,11 +117,12 @@ struct WelcomeView: View {
Text(pages[index].title)
.font(.title)
.fontWeight(.bold)
.foregroundStyle(Color.textPrimary)
.multilineTextAlignment(.center)
Text(pages[index].description)
.font(.body)
.foregroundStyle(.secondary)
.foregroundStyle(Color.textSecondary)
.multilineTextAlignment(.center)
.lineLimit(3)
}
@@ -126,7 +142,7 @@ struct WelcomeView: View {
HStack(spacing: DesignSystem.spacingSM) {
ForEach(0..<pages.count, id: \.self) { index in
Circle()
.fill(index == currentPage ? Color.accentColor : Color.gray.opacity(0.3))
.fill(index == currentPage ? Color.brandAccent : Color.textSecondary.opacity(0.3))
.frame(
width: index == currentPage ? 10 : 8,
height: index == currentPage ? 10 : 8
@@ -148,7 +164,7 @@ struct WelcomeView: View {
} label: {
Text("common_back".localized)
.font(.headline)
.foregroundStyle(.secondary)
.foregroundStyle(Color.textSecondary)
.frame(maxWidth: .infinity)
.padding(.vertical, DesignSystem.spacingMD)
}
@@ -171,17 +187,12 @@ struct WelcomeView: View {
)
.font(.headline)
.fontWeight(.semibold)
.foregroundStyle(.white)
.foregroundStyle(Color.textPrimary)
.frame(maxWidth: .infinity)
.padding(.vertical, DesignSystem.spacingMD)
.background(
LinearGradient(
colors: [.blue, .purple],
startPoint: .leading,
endPoint: .trailing
)
)
.background(AppTheme.warmGradient)
.cornerRadius(DesignSystem.cornerRadiusMD)
.shadow(color: AppTheme.buttonShadow, radius: 10, x: 0, y: 5)
}
}
@@ -193,7 +204,7 @@ struct WelcomeView: View {
} label: {
Text("common_skip".localized)
.font(.subheadline)
.foregroundStyle(.secondary)
.foregroundStyle(Color.textSecondary)
}
}
}