feat: Reimplement HomeView with a Super App layout, introducing WalletCard, ServiceGrid, PromoCarousel, and ActivityFeed components.

This commit is contained in:
Ho Ngoc Hai
2026-01-16 10:42:18 +07:00
parent 9df5c1da36
commit 334d66c91f
7 changed files with 828 additions and 182 deletions

View File

@@ -0,0 +1,193 @@
//
// ActivityFeed.swift
// AppClientBaseSwift
//
// Recent activity/transaction feed
// Feed hot đng/giao dch gn đây
//
import SwiftUI
// MARK: - Activity Item
// Item Activity
/// Activity/Transaction item model
/// Model item hot đng/giao dch
struct ActivityItem: Identifiable {
let id = UUID()
let title: String
let subtitle: String
let amount: Double
let icon: String
let color: Color
let time: String
/// Whether amount is positive (received)
/// Amount có dương (nhn tin) không
var isPositive: Bool {
amount >= 0
}
static let samples: [ActivityItem] = [
ActivityItem(
title: "Grab - Đặt xe",
subtitle: "Quận 1 → Quận 7",
amount: -45000,
icon: "car.fill",
color: .green,
time: "10:30"
),
ActivityItem(
title: "Nhận tiền",
subtitle: "Từ Nguyễn Văn A",
amount: 200000,
icon: "arrow.down.circle.fill",
color: .blue,
time: "Hôm qua"
),
ActivityItem(
title: "Thanh toán điện",
subtitle: "EVN HCM",
amount: -350000,
icon: "bolt.fill",
color: .yellow,
time: "20/01"
),
ActivityItem(
title: "Hoàn tiền",
subtitle: "Khuyến mãi đặt xe",
amount: 15000,
icon: "gift.fill",
color: .pink,
time: "19/01"
)
]
}
// MARK: - Activity Feed
// Feed Activity
/// Activity/Transaction feed list
/// List feed hot đng/giao dch
struct ActivityFeed: View {
// MARK: - Properties
/// Activity items
/// Các items hot đng
let activities: [ActivityItem]
/// Item tap callback
/// Callback tap item
var onActivityTap: ((ActivityItem) -> Void)?
// MARK: - Init
init(activities: [ActivityItem] = ActivityItem.samples, onActivityTap: ((ActivityItem) -> Void)? = nil) {
self.activities = activities
self.onActivityTap = onActivityTap
}
// MARK: - Body
var body: some View {
VStack(alignment: .leading, spacing: 16) {
// Header
HStack {
Text("Hoạt động gần đây")
.font(.headline)
.fontWeight(.semibold)
Spacer()
Button("Xem tất cả") {
// Show all activities
}
.font(.subheadline)
.foregroundStyle(.blue)
}
// Activity list
// Danh sách hot đng
VStack(spacing: 0) {
ForEach(activities) { activity in
ActivityRow(activity: activity)
.onTapGesture {
onActivityTap?(activity)
}
if activity.id != activities.last?.id {
Divider()
.padding(.leading, 56)
}
}
}
.background(Color.white)
.cornerRadius(16)
.shadow(color: .black.opacity(0.05), radius: 10, x: 0, y: 5)
}
}
}
// MARK: - Activity Row
// Row Activity
/// Individual activity row
/// Row hot đng riêng l
struct ActivityRow: View {
let activity: ActivityItem
var body: some View {
HStack(spacing: 14) {
// Icon
ZStack {
Circle()
.fill(activity.color.opacity(0.15))
.frame(width: 44, height: 44)
Image(systemName: activity.icon)
.font(.system(size: 18))
.foregroundStyle(activity.color)
}
// Content
// Ni dung
VStack(alignment: .leading, spacing: 4) {
Text(activity.title)
.font(.subheadline)
.fontWeight(.medium)
.foregroundStyle(.primary)
Text(activity.subtitle)
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
// Amount + Time
VStack(alignment: .trailing, spacing: 4) {
Text(activity.isPositive ? "+\(String.formatVND(activity.amount))" : String.formatVND(activity.amount))
.font(.subheadline)
.fontWeight(.semibold)
.foregroundStyle(activity.isPositive ? .green : .primary)
Text(activity.time)
.font(.caption)
.foregroundStyle(.secondary)
}
}
.padding(.horizontal, 16)
.padding(.vertical, 14)
}
}
// MARK: - Preview
#Preview {
ZStack {
Color.gray.opacity(0.1).ignoresSafeArea()
ActivityFeed()
.padding()
}
}

View File

@@ -0,0 +1,187 @@
//
// PromoCarousel.swift
// AppClientBaseSwift
//
// Promotional banners carousel
// Carousel banners khuyến mãi
//
import SwiftUI
// MARK: - Promo Item
// Item Promo
/// Promotional banner item
/// Item banner khuyến mãi
struct PromoItem: Identifiable {
let id = UUID()
let title: String
let subtitle: String
let gradientColors: [Color]
let icon: String
static let samples: [PromoItem] = [
PromoItem(
title: "Giảm 50% Đặt xe",
subtitle: "Áp dụng đến 31/01",
gradientColors: [.orange, .red],
icon: "car.fill"
),
PromoItem(
title: "Freeship Đồ ăn",
subtitle: "Đơn từ 50K",
gradientColors: [.green, .mint],
icon: "takeoutbag.and.cup.and.straw.fill"
),
PromoItem(
title: "Hoàn tiền 20%",
subtitle: "Thanh toán hóa đơn",
gradientColors: [.blue, .purple],
icon: "creditcard.fill"
)
]
}
// MARK: - Promo Carousel
// Carousel Promo
/// Promotional banners carousel
/// Carousel banners khuyến mãi
struct PromoCarousel: View {
// MARK: - Properties
/// Promo items to display
/// Các items promo hin th
let items: [PromoItem]
/// Current page index
/// Index trang hin ti
@State private var currentIndex = 0
/// Auto-scroll timer
/// Timer auto-scroll
@State private var timer: Timer?
// MARK: - Init
init(items: [PromoItem] = PromoItem.samples) {
self.items = items
}
// MARK: - Body
var body: some View {
VStack(spacing: 12) {
// Carousel
TabView(selection: $currentIndex) {
ForEach(Array(items.enumerated()), id: \.element.id) { index, item in
PromoCard(item: item)
.tag(index)
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
.frame(height: 140)
// Page indicators
// Indicators trang
HStack(spacing: 6) {
ForEach(0..<items.count, id: \.self) { index in
Circle()
.fill(index == currentIndex ? Color.accentColor : Color.gray.opacity(0.3))
.frame(width: index == currentIndex ? 8 : 6, height: index == currentIndex ? 8 : 6)
.animation(.easeInOut(duration: 0.2), value: currentIndex)
}
}
}
.onAppear {
startAutoScroll()
}
.onDisappear {
stopAutoScroll()
}
}
// MARK: - Methods
/// Start auto-scroll timer
/// Bt đu timer auto-scroll
private func startAutoScroll() {
timer = Timer.scheduledTimer(withTimeInterval: 4.0, repeats: true) { _ in
withAnimation(.easeInOut(duration: 0.5)) {
currentIndex = (currentIndex + 1) % items.count
}
}
}
/// Stop auto-scroll timer
/// Dng timer auto-scroll
private func stopAutoScroll() {
timer?.invalidate()
timer = nil
}
}
// MARK: - Promo Card
// Card Promo
/// Individual promo banner card
/// Card banner promo riêng l
struct PromoCard: View {
let item: PromoItem
var body: some View {
HStack {
// Content
// Ni dung
VStack(alignment: .leading, spacing: 8) {
Text(item.title)
.font(.headline)
.fontWeight(.bold)
.foregroundStyle(.white)
Text(item.subtitle)
.font(.subheadline)
.foregroundStyle(.white.opacity(0.8))
Spacer()
// CTA Button
Text("Xem ngay →")
.font(.caption)
.fontWeight(.semibold)
.foregroundStyle(.white)
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(.white.opacity(0.2))
.cornerRadius(12)
}
.padding(20)
Spacer()
// Icon
Image(systemName: item.icon)
.font(.system(size: 50))
.foregroundStyle(.white.opacity(0.3))
.padding(.trailing, 20)
}
.frame(maxWidth: .infinity)
.background(
LinearGradient(
colors: item.gradientColors,
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.cornerRadius(16)
.padding(.horizontal, 4)
}
}
// MARK: - Preview
#Preview {
PromoCarousel()
.padding()
}

View File

@@ -0,0 +1,119 @@
//
// ServiceGrid.swift
// AppClientBaseSwift
//
// Services grid with icons and labels
// Grid services vi icons và labels
//
import SwiftUI
// MARK: - Service Item
// Item Service
/// Service item model
/// Model item service
struct ServiceItem: Identifiable {
let id = UUID()
let icon: String
let title: String
let color: Color
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")
]
}
// MARK: - Service Grid
// Grid Service
/// Grid of service icons
/// Grid các icon service
struct ServiceGrid: View {
// MARK: - Properties
/// Services to display
/// Các services hin th
let services: [ServiceItem]
/// Service tap callback
/// Callback tap service
var onServiceTap: ((ServiceItem) -> Void)?
/// Grid columns
/// Các ct grid
private let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
// MARK: - Init
init(services: [ServiceItem] = ServiceItem.defaultServices, onServiceTap: ((ServiceItem) -> Void)? = nil) {
self.services = services
self.onServiceTap = onServiceTap
}
// MARK: - Body
var body: some View {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(services) { service in
ServiceGridItem(service: service)
.onTapGesture {
onServiceTap?(service)
}
}
}
}
}
// MARK: - Service Grid Item
// Item Grid Service
/// Individual service grid item
/// Item grid service riêng l
struct ServiceGridItem: View {
let service: ServiceItem
var body: some View {
VStack(spacing: 10) {
// Icon container
// Container icon
ZStack {
RoundedRectangle(cornerRadius: 16)
.fill(service.color.opacity(0.15))
.frame(width: 56, height: 56)
Image(systemName: service.icon)
.font(.system(size: 24))
.foregroundStyle(service.color)
}
// Label
Text(service.title)
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.primary)
.lineLimit(1)
}
}
}
// MARK: - Preview
#Preview {
ServiceGrid()
.padding()
}

View File

@@ -0,0 +1,169 @@
//
// WalletCard.swift
// AppClientBaseSwift
//
// Wallet card with balance and quick actions
// Card ví vi s dư và quick actions
//
import SwiftUI
// MARK: - Wallet Card
// Card Ví
/// Wallet card displaying balance and quick actions
/// Card ví hin th s dư và quick actions
struct WalletCard: View {
// MARK: - Properties
/// Current balance
/// S dư hin ti
let balance: Double
/// Whether balance is hidden
/// S dư có b n không
@State private var isBalanceHidden = false
/// Quick action callback
/// Callback quick action
var onQuickAction: ((QuickAction) -> Void)?
// MARK: - Quick Actions Enum
enum QuickAction: String, CaseIterable {
case topUp = "Nạp tiền"
case transfer = "Chuyển tiền"
case payment = "Thanh toán"
var icon: String {
switch self {
case .topUp: return "plus.circle.fill"
case .transfer: return "arrow.left.arrow.right.circle.fill"
case .payment: return "qrcode"
}
}
}
// MARK: - Body
var body: some View {
VStack(spacing: 20) {
// Balance section
// Phn s dư
balanceSection
// Quick actions
// Quick actions
quickActionsSection
}
.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
)
)
.cornerRadius(20)
.shadow(color: Color(red: 0.4, green: 0.3, blue: 0.7).opacity(0.4), radius: 15, x: 0, y: 10)
}
// MARK: - Subviews
/// Balance display section
/// Phn hin th s dư
private var balanceSection: some View {
HStack {
VStack(alignment: .leading, spacing: 6) {
Text("Số dư khả dụng")
.font(.subheadline)
.foregroundStyle(.white.opacity(0.8))
HStack(spacing: 8) {
if isBalanceHidden {
Text("••••••••")
.font(.system(size: 28, weight: .bold, design: .rounded))
.foregroundStyle(.white)
} else {
Text(String.formatVND(balance))
.font(.system(size: 28, weight: .bold, design: .rounded))
.foregroundStyle(.white)
}
Button {
withAnimation(.easeInOut(duration: 0.2)) {
isBalanceHidden.toggle()
}
} label: {
Image(systemName: isBalanceHidden ? "eye.slash.fill" : "eye.fill")
.font(.subheadline)
.foregroundStyle(.white.opacity(0.8))
}
}
}
Spacer()
// Points badge
// Badge đim
VStack(alignment: .trailing, spacing: 4) {
HStack(spacing: 4) {
Image(systemName: "star.circle.fill")
.foregroundStyle(.yellow)
Text("1,250")
.fontWeight(.semibold)
}
.font(.subheadline)
.foregroundStyle(.white)
Text("điểm thưởng")
.font(.caption)
.foregroundStyle(.white.opacity(0.7))
}
}
}
/// Quick actions buttons section
/// Phn buttons quick actions
private var quickActionsSection: some View {
HStack(spacing: 0) {
ForEach(QuickAction.allCases, id: \.self) { action in
Button {
onQuickAction?(action)
} label: {
VStack(spacing: 8) {
ZStack {
Circle()
.fill(.white.opacity(0.2))
.frame(width: 48, height: 48)
Image(systemName: action.icon)
.font(.title2)
.foregroundStyle(.white)
}
Text(action.rawValue)
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.white)
}
}
.frame(maxWidth: .infinity)
}
}
}
}
// MARK: - Preview
#Preview {
ZStack {
Color.gray.opacity(0.1).ignoresSafeArea()
WalletCard(balance: 1_250_000)
.padding()
}
}

View File

@@ -2,8 +2,8 @@
// HomeView.swift
// AppClientBaseSwift
//
// Home screen view with MVVM binding
// View màn hình Home vi MVVM binding
// Super App style home screen
// Màn hình home phong cách Super App
//
import SwiftUI
@@ -11,233 +11,211 @@ import SwiftUI
// MARK: - Home View
// View Home
/// Home tab main view
/// View chính ca tab Home
/// Super App style home view with wallet, services, promos
/// View home phong cách Super App vi ví, services, promos
struct HomeView: View {
// MARK: - Properties
/// ViewModel for home screen
/// ViewModel cho màn hình home
@StateObject private var viewModel = HomeViewModel()
/// Auth manager for user info
/// Auth manager cho thông tin user
@StateObject private var authManager = AuthManager.shared
/// Mock balance
/// S dư mock
private let balance: Double = 1_250_000
// MARK: - Body
var body: some View {
NavigationStack {
ScrollView {
VStack(spacing: DesignSystem.spacingLG) {
// Greeting section
// Phn li chào
greetingSection
ScrollView(showsIndicators: false) {
VStack(spacing: 24) {
// Header
headerSection
.padding(.horizontal, 20)
// Featured carousel
// Carousel ni bt
if !viewModel.featuredItems.isEmpty {
featuredSection
// Wallet Card
// Card Ví
WalletCard(balance: balance) { action in
handleQuickAction(action)
}
.padding(.horizontal, 20)
// Services Grid
// Grid Services
VStack(alignment: .leading, spacing: 16) {
Text("Dịch vụ")
.font(.headline)
.fontWeight(.semibold)
.padding(.horizontal, 20)
ServiceGrid { service in
handleServiceTap(service)
}
.padding(.horizontal, 20)
}
// Main content
// Ni dung chính
contentSection
// Promo Carousel
// Carousel Promo
VStack(alignment: .leading, spacing: 16) {
HStack {
Text("Ưu đãi hấp dẫn")
.font(.headline)
.fontWeight(.semibold)
Spacer()
Button("Xem tất cả") {
// Show all promos
}
.font(.subheadline)
.foregroundStyle(.blue)
}
.padding(.horizontal, 20)
PromoCarousel()
.padding(.horizontal, 16)
}
// Activity Feed
// Feed Hot đng
ActivityFeed { activity in
handleActivityTap(activity)
}
.padding(.horizontal, 20)
// Bottom spacing
Spacer(minLength: 20)
}
.padding(.horizontal, DesignSystem.spacingMD)
.padding(.vertical, DesignSystem.spacingSM)
.padding(.top, 10)
}
.refreshable {
await viewModel.refresh()
}
.navigationTitle("tab_home".localized)
.navigationBarTitleDisplayMode(.large)
.loadingOverlay(viewModel.isLoading)
.task {
await viewModel.loadData()
}
.alert("error_title".localized, isPresented: .constant(viewModel.errorMessage != nil)) {
Button("common_ok".localized) {
viewModel.errorMessage = nil
.background(Color(red: 0.96, green: 0.97, blue: 0.98))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
HStack(spacing: 16) {
// QR Code
Button {
// Show QR scanner
} label: {
Image(systemName: "qrcode.viewfinder")
.font(.title3)
.foregroundStyle(.primary)
}
// Notifications
Button {
// Show notifications
} label: {
Image(systemName: "bell.badge")
.font(.title3)
.foregroundStyle(.primary)
}
}
}
} message: {
Text(viewModel.errorMessage ?? "")
}
}
}
// MARK: - Subviews
/// Greeting section at top
/// Phn li chào đu
private var greetingSection: some View {
HStack {
VStack(alignment: .leading, spacing: DesignSystem.spacingXS) {
Text(viewModel.greeting)
.font(.title2)
.fontWeight(.bold)
/// Header with user info
/// Header vi thông tin user
private var headerSection: some View {
HStack(spacing: 14) {
// Avatar
avatarView
Text("home_subtitle".localized)
// Greeting
VStack(alignment: .leading, spacing: 4) {
Text(greetingText)
.font(.subheadline)
.foregroundStyle(.secondary)
Text(authManager.currentUser?.name ?? "Người dùng")
.font(.title3)
.fontWeight(.bold)
}
Spacer()
// Notification button
// Nút thông báo
Button {
// Handle notification tap
// X lý tap thông báo
} label: {
Image(systemName: "bell.badge")
.font(.title2)
.foregroundStyle(.primary)
}
}
.padding(.vertical, DesignSystem.spacingSM)
}
/// Featured items carousel section
/// Phn carousel items ni bt
private var featuredSection: some View {
VStack(alignment: .leading, spacing: DesignSystem.spacingSM) {
Text("home_featured".localized)
.font(.headline)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: DesignSystem.spacingMD) {
ForEach(viewModel.featuredItems) { item in
FeaturedCard(item: item)
.onTapGesture {
viewModel.handleItemTap(item)
}
}
/// User avatar view
/// View avatar user
private var avatarView: some View {
Group {
if let avatarUrl = authManager.currentUser?.avatarUrl,
let url = URL(string: avatarUrl) {
AsyncImage(url: url) { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
} placeholder: {
avatarPlaceholder
}
} else {
avatarPlaceholder
}
}
.frame(width: 50, height: 50)
.clipShape(Circle())
}
/// Main content section with items
/// Phn ni dung chính vi các items
private var contentSection: some View {
VStack(alignment: .leading, spacing: DesignSystem.spacingSM) {
Text("home_explore".localized)
.font(.headline)
LazyVStack(spacing: DesignSystem.spacingMD) {
ForEach(viewModel.items) { item in
HomeItemRow(item: item)
.onTapGesture {
viewModel.handleItemTap(item)
}
}
}
}
}
}
// MARK: - Featured Card
// Card ni bt
/// Featured item card view
/// View card item ni bt
struct FeaturedCard: View {
let item: HomeItem
var body: some View {
VStack(alignment: .leading, spacing: DesignSystem.spacingSM) {
// Placeholder image
// nh placeholder
RoundedRectangle(cornerRadius: DesignSystem.cornerRadiusMD)
/// Avatar placeholder
/// Placeholder avatar
private var avatarPlaceholder: some View {
ZStack {
Circle()
.fill(
LinearGradient(
colors: [.blue.opacity(0.8), .purple.opacity(0.8)],
colors: [.blue, .purple],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.frame(width: 280, height: 140)
.overlay(
Image(systemName: "star.fill")
.font(.largeTitle)
.foregroundStyle(.white)
)
VStack(alignment: .leading, spacing: DesignSystem.spacingXS) {
Text(item.title)
.font(.subheadline)
.fontWeight(.semibold)
.lineLimit(1)
Text(item.subtitle)
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
}
.frame(width: 280, alignment: .leading)
Text(authManager.currentUser?.initials ?? "U")
.font(.headline)
.fontWeight(.bold)
.foregroundStyle(.white)
}
}
}
// MARK: - Home Item Row
// Row item Home
/// Home item row view
/// View row item home
struct HomeItemRow: View {
let item: HomeItem
var body: some View {
HStack(spacing: DesignSystem.spacingMD) {
// Icon
RoundedRectangle(cornerRadius: DesignSystem.cornerRadiusSM)
.fill(Color.accentColor.opacity(0.1))
.frame(width: 56, height: 56)
.overlay(
Image(systemName: iconForCategory(item.category))
.font(.title2)
.foregroundStyle(Color.accentColor)
)
// Content
// Ni dung
VStack(alignment: .leading, spacing: DesignSystem.spacingXS) {
Text(item.title)
.font(.subheadline)
.fontWeight(.medium)
Text(item.subtitle)
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
// Arrow
Image(systemName: "chevron.right")
.font(.caption)
.foregroundStyle(.tertiary)
}
.padding(DesignSystem.spacingMD)
.cardStyle()
}
/// Get SF Symbol icon for category
/// Ly icon SF Symbol cho category
/// - Parameter category: Item category / Category ca item
/// - Returns: SF Symbol name / Tên SF Symbol
private func iconForCategory(_ category: String) -> String {
switch category {
case "explore":
return "map"
case "promo":
return "tag.fill"
case "points":
return "star.circle.fill"
case "recommend":
return "heart.fill"
/// Greeting text based on time of day
/// Text li chào da trên thi gian trong ngày
private var greetingText: String {
let hour = Calendar.current.component(.hour, from: Date())
switch hour {
case 5..<12:
return "Chào buổi sáng 👋"
case 12..<18:
return "Chào buổi chiều ☀️"
default:
return "circle.fill"
return "Chào buổi tối 🌙"
}
}
// MARK: - Methods
/// Handle quick action tap
/// X lý tap quick action
private func handleQuickAction(_ action: WalletCard.QuickAction) {
print("Quick action: \(action.rawValue)")
}
/// Handle service tap
/// X lý tap service
private func handleServiceTap(_ service: ServiceItem) {
print("Service: \(service.title)")
}
/// Handle activity tap
/// X lý tap activity
private func handleActivityTap(_ activity: ActivityItem) {
print("Activity: \(activity.title)")
}
}
// MARK: - Preview

View File

@@ -2,7 +2,7 @@ Test Account
Tài khoản: hongochai10@icloud.com
Mật Khẩu: Velik@2026
demo@goodgo.vn / Demo@123.
admin@goodgo.com / 123456
dotnet build -c Debug -f net10.0-ios -t:Run -p:_DeviceName=:v2:udid=D8A27496-0AFB-4314-96EC-E8B685575330
curl -s -X POST "http