diff --git a/apps/app-client-base-swift/ARCHITECTURE.md b/apps/app-client-base-swift/ARCHITECTURE.md new file mode 100644 index 00000000..aebfdf68 --- /dev/null +++ b/apps/app-client-base-swift/ARCHITECTURE.md @@ -0,0 +1,301 @@ +# Architecture / Kiến Trúc + +> **EN**: Detailed architecture documentation for AppClientBaseSwift iOS application. +> **VI**: Tài liệu kiến trúc chi tiết cho ứng dụng iOS AppClientBaseSwift. + +## Overview / Tổng Quan + +AppClientBaseSwift is a native iOS application built using **MVVM (Model-View-ViewModel)** architecture pattern with **SwiftUI** for declarative UI. The app follows Apple's modern development best practices including: + +- **Swift Concurrency** (async/await) +- **Combine** for reactive data binding +- **Protocol-oriented programming** for testability +- **Keychain Services** for secure storage + +## Architecture Diagram / Sơ Đồ Kiến Trúc + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ PRESENTATION LAYER │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ SwiftUI Views │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │ SplashView │ │ HomeView │ │ ExploreView │ │ ProfileView │ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │ LoginView │ │RegisterView │ │ForgotPasswd │ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ @StateObject / @EnvironmentObject │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ ViewModels (@MainActor) │ │ +│ │ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ │ +│ │ │ AuthViewModel │ │ HomeViewModel │ │ProfileViewModel│ │ │ +│ │ │ @Published │ │ @Published │ │ @Published │ │ │ +│ │ │ - isLoading │ │ - items │ │ - user │ │ │ +│ │ │ - errorMessage │ │ - greeting │ │ - isEditing │ │ │ +│ │ └────────────────┘ └────────────────┘ └────────────────┘ │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + Dependency Injection (Protocol-based) + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ SERVICE LAYER │ +│ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │ +│ │ APIService │ │ AuthManager │ │ +│ │ ┌───────────────────────┐ │ │ ┌───────────────────────┐ │ │ +│ │ │ APIServiceProtocol │ │ │ │ @Published authState │ │ │ +│ │ │ - request() │ │ │ │ - login() │ │ │ +│ │ │ - get(), post() │ │ │ │ - register() │ │ │ +│ │ │ - put(), delete() │ │ │ │ - logout() │ │ │ +│ │ └───────────────────────┘ │ │ │ - refreshToken() │ │ │ +│ │ URLSession │ │ │ Keychain │ │ │ +│ └─────────────────────────────┘ └─────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ DATA LAYER │ +│ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │ +│ │ Models │ │ Constants │ │ +│ │ ┌───────────────────────┐ │ │ ┌───────────────────────┐ │ │ +│ │ │ User (Codable) │ │ │ │ APIConfig │ │ │ +│ │ │ HomeItem │ │ │ │ AppConstants │ │ │ +│ │ │ AuthState │ │ │ │ StorageKeys │ │ │ +│ │ └───────────────────────┘ │ │ │ DesignSystem │ │ │ +│ └─────────────────────────────┘ └─────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +## Component Details / Chi Tiết Component + +### 1. Presentation Layer + +#### Views +| Component | Responsibility / Trách nhiệm | +|-----------|------------------------------| +| `SplashView` | Animated splash screen, delayed navigation | +| `ContentView` | Root TabView container, auth state routing | +| `AuthContainerView` | Auth flow navigation (Login/Register/Forgot) | +| `HomeView` | Home tab with greeting, promo, services | +| `ExploreView` | Discovery and search features | +| `ProfileView` | User profile and settings | + +#### ViewModels +```swift +@MainActor +final class HomeViewModel: ObservableObject { + // Reactive properties + @Published var isLoading: Bool = false + @Published var items: [HomeItem] = [] + @Published var errorMessage: String? + + // Dependencies injected via init + private let apiService: APIServiceProtocol + + // Async methods using Swift Concurrency + func loadData() async { ... } +} +``` + +### 2. Service Layer + +#### APIService +HTTP client following **Single Responsibility Principle**: + +```swift +protocol APIServiceProtocol { + func request( + endpoint: String, + method: HTTPMethod, + body: Encodable?, + headers: [String: String]? + ) async throws -> T +} +``` + +**Features:** +- Generic request/response handling +- Automatic JSON encoding/decoding (snake_case ↔ camelCase) +- Bearer token injection +- HTTP status code handling +- Error categorization + +#### AuthManager +Singleton for authentication state: + +```swift +final class AuthManager: ObservableObject { + @MainActor static let shared = AuthManager() + + @Published var authState: AuthState = .unknown + + // Keychain-backed tokens + var accessToken: String? { get } + var refreshToken: String? { get } +} +``` + +**AuthState Enum:** +```swift +enum AuthState { + case unknown // Initial state / Trạng thái khởi tạo + case unauthenticated // Logged out / Chưa đăng nhập + case authenticated(User) // Logged in / Đã đăng nhập +} +``` + +### 3. Data Layer + +#### Models +```swift +struct User: Codable, Identifiable, Equatable { + let id: String + let email: String + let name: String + let avatarUrl: String? + let phoneNumber: String? + let isEmailVerified: Bool + let createdAt: Date? + let updatedAt: Date? +} +``` + +#### Constants +Organized into semantic enums: +- `APIConfig`: Base URL, version, timeout +- `AppConstants`: App name, bundle ID, keychain service +- `StorageKeys`: UserDefaults/Keychain keys +- `DesignSystem`: Spacing, corner radius, font sizes + +## Data Flow / Luồng Dữ Liệu + +```mermaid +sequenceDiagram + participant V as View + participant VM as ViewModel + participant S as Service + participant API as Backend API + + V->>VM: User Action (button tap) + VM->>VM: Set isLoading = true + VM->>S: await service.request() + S->>API: HTTP Request + API-->>S: JSON Response + S-->>VM: Decoded Model + VM->>VM: Update @Published + VM-->>V: SwiftUI re-render +``` + +## Authentication Flow / Luồng Xác Thực + +```mermaid +stateDiagram-v2 + [*] --> Unknown: App Launch + Unknown --> Authenticated: Token Found + Valid + Unknown --> Unauthenticated: No Token + + Unauthenticated --> Login: Show Login + Login --> Authenticated: Login Success + Login --> Register: Navigate + Register --> Authenticated: Register Success + + Authenticated --> HomeScreen: Show Main App + HomeScreen --> Unauthenticated: Logout + + Authenticated --> TokenRefresh: Token Expired + TokenRefresh --> Authenticated: Refresh Success + TokenRefresh --> Unauthenticated: Refresh Failed +``` + +## Design Decisions / Quyết Định Thiết Kế + +### 1. Why MVVM? / Tại Sao MVVM? + +| Benefit / Lợi ích | Description / Mô tả | +|-------------------|---------------------| +| Testability | ViewModel có thể test độc lập không cần UI | +| Separation of Concerns | View chỉ hiển thị, logic nằm ở ViewModel | +| SwiftUI Compatibility | `@ObservableObject` + `@Published` native | +| Reactive Updates | Combine-based automatic UI refresh | + +### 2. Why Protocol-based DI? + +```swift +// Protocol enables mocking for tests +protocol APIServiceProtocol { + func get(endpoint: String) async throws -> T +} + +// Production implementation +final class APIService: APIServiceProtocol { ... } + +// Test mock +final class MockAPIService: APIServiceProtocol { ... } +``` + +### 3. Why Keychain over UserDefaults? + +| Keychain | UserDefaults | +|----------|--------------| +| ✅ Encrypted at rest | ❌ Plain text | +| ✅ Secure enclave | ❌ Accessible | +| ✅ App-specific | ❌ Shared prefs | + +### 4. Why @MainActor on ViewModels? + +```swift +@MainActor +final class HomeViewModel: ObservableObject { ... } +``` + +- Ensures all `@Published` updates happen on main thread +- Prevents concurrency issues with SwiftUI +- Explicit thread safety contract + +## Security Architecture / Kiến Trúc Bảo Mật + +``` +┌────────────────────────────────────────────────────────────┐ +│ SECURITY LAYERS │ +├────────────────────────────────────────────────────────────┤ +│ Layer 1: Transport Security (HTTPS/TLS) │ +│ • All API calls use HTTPS │ +│ • Certificate pinning (TODO) │ +├────────────────────────────────────────────────────────────┤ +│ Layer 2: Token Security (Keychain) │ +│ • Access token stored in Keychain │ +│ • Refresh token stored in Keychain │ +│ • kSecClassGenericPassword protection │ +├────────────────────────────────────────────────────────────┤ +│ Layer 3: Session Security │ +│ • Token expiry validation │ +│ • Automatic token refresh │ +│ • Secure logout (clear all tokens) │ +├────────────────────────────────────────────────────────────┤ +│ Layer 4: Input Validation │ +│ • Email format validation │ +│ • Password strength checking │ +│ • Form field sanitization │ +└────────────────────────────────────────────────────────────┘ +``` + +## Future Considerations / Hướng Phát Triển + +| Feature | Priority | Description | +|---------|----------|-------------| +| Certificate Pinning | High | TLS certificate validation | +| Biometric Auth | High | Face ID / Touch ID login | +| Offline Mode | Medium | Local caching with SwiftData | +| Push Notifications | Medium | APNs integration | +| Analytics | Low | Event tracking system | + +## Related Documentation / Tài Liệu Liên Quan + +- [README.md](./README.md) - Quick start guide +- [Swift Enterprise Architect Skill](../../.agent/skills/swift-enterprise-architect/SKILL.md) +- [Swift Security Skill](../../.agent/skills/swift-security/SKILL.md) +- [Swift Networking Skill](../../.agent/skills/swift-networking/SKILL.md) diff --git a/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift.xcodeproj/project.xcworkspace/xcuserdata/velikho.xcuserdatad/UserInterfaceState.xcuserstate b/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift.xcodeproj/project.xcworkspace/xcuserdata/velikho.xcuserdatad/UserInterfaceState.xcuserstate index 4d5c84cf..b83ef307 100644 Binary files a/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift.xcodeproj/project.xcworkspace/xcuserdata/velikho.xcuserdatad/UserInterfaceState.xcuserstate and b/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift.xcodeproj/project.xcworkspace/xcuserdata/velikho.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift/Core/Constants/Constants.swift b/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift/Core/Constants/Constants.swift index 9b5041a9..c857cd39 100644 --- a/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift/Core/Constants/Constants.swift +++ b/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift/Core/Constants/Constants.swift @@ -14,19 +14,28 @@ import SwiftUI /// API configuration constants /// Các hằng số cấu hình API enum APIConfig { - /// Base URL for API requests - /// URL gốc cho các request API - static let baseURL = "https://api.goodgo.vn" + /// Base URL for API requests (Traefik gateway) + /// URL gốc cho các request API (Traefik gateway) + static let baseURL = "http://localhost" /// API version prefix /// Tiền tố phiên bản API static let apiVersion = "/api/v1" + /// OAuth2 token endpoint (no version prefix) + /// OAuth2 token endpoint (không có version prefix) + static let tokenEndpoint = "/connect/token" + + /// OAuth2 scope for authentication + /// OAuth2 scope cho xác thực + static let oauthScope = "openid profile email offline_access" + /// Request timeout in seconds /// Thời gian timeout request (giây) static let timeout: TimeInterval = 30.0 } + // MARK: - App Constants // Hằng số ứng dụng diff --git a/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift/Services/APIService.swift b/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift/Services/APIService.swift index c6624fc6..962c477b 100644 --- a/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift/Services/APIService.swift +++ b/apps/app-client-base-swift/AppClientBaseSwift/AppClientBaseSwift/Services/APIService.swift @@ -51,6 +51,39 @@ enum APIError: Error, LocalizedError { } } +// MARK: - OAuth2 Response Models +// Models response OAuth2 + +/// OAuth2 token response +/// Response token OAuth2 +struct OAuthTokenResponse: Decodable { + let accessToken: String + let tokenType: String + let expiresIn: Int + let refreshToken: String? + let scope: String? + + enum CodingKeys: String, CodingKey { + case accessToken = "access_token" + case tokenType = "token_type" + case expiresIn = "expires_in" + case refreshToken = "refresh_token" + case scope + } +} + +/// OAuth2 error response +/// Response lỗi OAuth2 +struct OAuthErrorResponse: Decodable { + let error: String + let errorDescription: String? + + enum CodingKeys: String, CodingKey { + case error + case errorDescription = "error_description" + } +} + // MARK: - HTTP Method // Phương thức HTTP @@ -232,4 +265,61 @@ final class APIService: APIServiceProtocol { func delete(endpoint: String) async throws -> T { try await request(endpoint: endpoint, method: .delete, body: nil as String?, headers: nil) } + + // MARK: - OAuth2 Methods + // Các phương thức OAuth2 + + /// POST form-urlencoded request (for OAuth2 token endpoint) + /// Request POST dạng form-urlencoded (cho OAuth2 token endpoint) + /// - Parameters: + /// - endpoint: Token endpoint path / Đường dẫn token endpoint + /// - formData: Form data dictionary / Dictionary form data + /// - Returns: Decoded response / Response đã giải mã + func postForm(endpoint: String, formData: [String: String]) async throws -> T { + guard let url = URL(string: APIConfig.baseURL + endpoint) else { + throw APIError.invalidURL + } + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + request.timeoutInterval = APIConfig.timeout + + // Encode form data + // Mã hóa form data + let formBody = formData.map { key, value in + let escapedValue = value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" + return "\(key)=\(escapedValue)" + }.joined(separator: "&") + request.httpBody = formBody.data(using: .utf8) + + // Perform request + // Thực hiện request + let (data, response) = try await session.data(for: request) + + guard let httpResponse = response as? HTTPURLResponse else { + throw APIError.unknown + } + + // Handle OAuth2 error responses + // Xử lý OAuth2 error responses + if httpResponse.statusCode != 200 { + if let errorResponse = try? decoder.decode(OAuthErrorResponse.self, from: data) { + throw APIError.serverError( + statusCode: httpResponse.statusCode, + message: errorResponse.errorDescription ?? errorResponse.error + ) + } + let message = String(data: data, encoding: .utf8) + throw APIError.serverError(statusCode: httpResponse.statusCode, message: message) + } + + do { + return try decoder.decode(T.self, from: data) + } catch { + throw APIError.decodingError(error) + } + } + try await request(endpoint: endpoint, method: .delete, body: nil as String?, headers: nil) + } } diff --git a/apps/app-client-base-swift/README.md b/apps/app-client-base-swift/README.md index 4c5146f2..1277757f 100644 --- a/apps/app-client-base-swift/README.md +++ b/apps/app-client-base-swift/README.md @@ -1,164 +1,290 @@ -# App Client Base Swift +# App Client Base Swift / Ứng Dụng Client iOS -Ứng dụng iOS native cho nền tảng GoodGo, xây dựng bằng Swift và SwiftUI. +> **EN**: Native iOS client application for GoodGo platform, built with Swift and SwiftUI following MVVM architecture. +> **VI**: Ứng dụng iOS native cho nền tảng GoodGo, xây dựng bằng Swift và SwiftUI theo kiến trúc MVVM. -## 📱 Tổng quan +## 📱 Features / Tính Năng -App client iOS native sử dụng kiến trúc MVVM với SwiftUI, tương tự như `app-client-base-net` (MAUI) nhưng dành riêng cho nền tảng Apple. +| Feature / Tính năng | Description / Mô tả | +|---------------------|---------------------| +| 🔐 Authentication | Login, Register, Forgot Password với form validation | +| 🏠 Home Dashboard | Greeting động, Featured items, Activity feed | +| 🔍 Explore | Khám phá địa điểm và dịch vụ | +| 👤 Profile | Quản lý thông tin cá nhân và cài đặt | +| 🌓 Dark Mode | Hỗ trợ chế độ tối tự động | +| 🌐 i18n | Đa ngôn ngữ (Tiếng Việt & English) | -## 🛠️ Công nghệ +## 🛠️ Tech Stack / Công Nghệ -| Công nghệ | Phiên bản | Mục đích | -|-----------|-----------|----------| -| Swift | 5.9+ | Ngôn ngữ lập trình | -| SwiftUI | iOS 15+ | UI Framework | -| Xcode | 15+ | IDE | -| URLSession | Native | Networking | -| Keychain | Native | Secure Storage | +| Technology | Version | Purpose / Mục đích | +|------------|---------|-------------------| +| Swift | 5.9+ | Primary language / Ngôn ngữ chính | +| SwiftUI | iOS 15+ | Declarative UI framework | +| Xcode | 15.0+ | IDE development | +| URLSession | Native | HTTP networking | +| Keychain | Native | Secure token storage / Lưu trữ token bảo mật | +| Combine | Native | Reactive programming | -## 📂 Cấu trúc thư mục +## � Prerequisites / Yêu Cầu + +- **macOS**: 14.0+ (Sonoma) +- **Xcode**: 15.0+ +- **iOS Target**: 15.0+ +- **Apple Developer Account**: Required for device deployment / Cần thiết cho deploy lên thiết bị + +## 🚀 Quick Start / Bắt Đầu Nhanh + +### 1. Clone và mở project +```bash +cd apps/app-client-base-swift +open AppClientBaseSwift/AppClientBaseSwift.xcodeproj +``` + +### 2. Chọn Simulator +- Xcode menu: **Product > Destination > iPhone 15 Pro** (hoặc simulator khác) + +### 3. Build và Run +```bash +# Sử dụng shortcut +⌘R (Command + R) + +# Hoặc từ terminal +xcodebuild -project AppClientBaseSwift/AppClientBaseSwift.xcodeproj \ + -scheme AppClientBaseSwift \ + -destination 'platform=iOS Simulator,name=iPhone 15 Pro' \ + build +``` + +### 4. Mock Login (để test) +``` +Email: admin@goodgo.com +Password: 123456 +``` + +## 📂 Project Structure / Cấu Trúc Project ``` AppClientBaseSwift/ ├── App/ -│ └── AppClientBaseSwiftApp.swift # @main entry point +│ └── AppClientBaseSwiftApp.swift # @main entry point +│ ├── Core/ │ ├── Constants/ -│ │ └── Constants.swift # App-wide constants +│ │ └── Constants.swift # API, App, Storage, DesignSystem │ └── Extensions/ -│ ├── View+Extensions.swift # SwiftUI View helpers -│ └── String+Extensions.swift # String utilities +│ ├── View+Extensions.swift # SwiftUI modifiers +│ └── String+Extensions.swift # Validation, formatting +│ ├── Models/ -│ └── User.swift # User data model -├── ViewModels/ -│ └── HomeViewModel.swift # MVVM ViewModel +│ └── User.swift # User entity + extensions +│ +├── ViewModels/ # MVVM ViewModels +│ ├── AuthViewModel.swift # Login/Register/ForgotPassword +│ ├── HomeViewModel.swift # Home screen logic +│ └── ProfileViewModel.swift # Profile management +│ ├── Views/ -│ └── Screens/ -│ ├── ContentView.swift # Main tab container -│ ├── WelcomeView.swift # Onboarding screen -│ ├── HomeView.swift # Home tab -│ ├── ExploreView.swift # Explore tab -│ └── ProfileView.swift # Profile tab +│ ├── Auth/ # Authentication screens +│ │ ├── AuthContainerView.swift # Auth navigation container +│ │ ├── LoginView.swift # Login UI +│ │ ├── RegisterView.swift # Registration UI +│ │ └── ForgotPasswordView.swift # Password reset UI +│ │ +│ ├── Home/ # Home components +│ │ ├── WalletCard.swift # Wallet balance card +│ │ ├── PromoCarousel.swift # Promotions carousel +│ │ ├── ServiceGrid.swift # Services grid +│ │ └── ActivityFeed.swift # Recent activities +│ │ +│ └── Screens/ # Main screens +│ ├── ContentView.swift # Root container + TabBar +│ ├── SplashView.swift # Splash animation +│ ├── WelcomeView.swift # Onboarding +│ ├── HomeView.swift # Home tab +│ ├── ExploreView.swift # Explore tab +│ └── ProfileView.swift # Profile tab +│ ├── Services/ -│ ├── APIService.swift # HTTP client -│ └── AuthManager.swift # Authentication -├── Resources/ -│ ├── Assets.xcassets/ # Images & Colors -│ ├── en.lproj/ # English strings -│ └── vi.lproj/ # Vietnamese strings -└── Info.plist # App configuration +│ ├── APIService.swift # HTTP client với URLSession +│ └── AuthManager.swift # Auth state + Keychain +│ +└── Resources/ + ├── Assets.xcassets/ # Images & Colors + ├── en.lproj/ # English localization + └── vi.lproj/ # Vietnamese localization ``` -## 🚀 Bắt đầu - -### Yêu cầu - -- macOS 14.0+ (Sonoma) -- Xcode 15.0+ -- iOS 15.0+ target - -### Mở project - -1. Mở Xcode -2. Chọn **File > Open** hoặc nhấn `⌘O` -3. Chọn thư mục `app-client-base-swift` -4. Build và chạy trên Simulator (`⌘R`) - -### Tạo Xcode Project (Nếu chưa có) - -Do project này chỉ chứa source files, bạn cần tạo Xcode project: - -```bash -# Mở Xcode và tạo project mới -# 1. File > New > Project -# 2. Chọn "App" template -# 3. Product Name: AppClientBaseSwift -# 4. Team: Chọn team của bạn -# 5. Organization Identifier: vn.goodgo -# 6. Interface: SwiftUI -# 7. Language: Swift -# 8. Lưu vào thư mục app-client-base-swift -# 9. Xóa các file generated và thay thế bằng files trong thư mục này -``` - -## 🎨 Kiến trúc +## 🎨 Architecture / Kiến Trúc ### MVVM Pattern ``` ┌─────────────────────────────────────────────────────────────┐ -│ VIEW (SwiftUI) │ -│ ContentView, HomeView, ExploreView, ProfileView │ +│ VIEW (SwiftUI) │ +│ HomeView, ProfileView, AuthContainerView, LoginView... │ ├─────────────────────────────────────────────────────────────┤ -│ │ -│ ┌──────────────────────────────────┐ │ -│ │ @StateObject │ │ -│ │ @EnvironmentObject │ │ -│ └──────────────────────────────────┘ │ +│ @StateObject / @EnvironmentObject │ │ │ │ ├────────────────────────────▼────────────────────────────────┤ -│ VIEWMODEL │ -│ HomeViewModel (ObservableObject) │ -│ - @Published properties │ -│ - async/await methods │ +│ VIEWMODEL (ObservableObject) │ +│ HomeViewModel, AuthViewModel, ProfileViewModel │ +│ • @Published properties for reactive UI │ +│ • async/await methods for data loading │ +│ • Business logic and validation │ ├─────────────────────────────────────────────────────────────┤ -│ │ -│ ┌──────────────────────────────────┐ │ -│ │ Protocol / Dependency │ │ -│ │ Injection │ │ -│ └──────────────────────────────────┘ │ +│ Protocol-based Dependency Injection │ │ │ │ ├────────────────────────────▼────────────────────────────────┤ -│ SERVICES │ -│ APIService, AuthManager │ +│ SERVICES │ +│ APIService (HTTP) • AuthManager (Auth State + Keychain) │ └─────────────────────────────────────────────────────────────┘ ``` -### Key Features +### Authentication Flow -- **SwiftUI**: UI declarative hiện đại -- **Async/Await**: Xử lý bất đồng bộ native -- **Keychain**: Lưu trữ token bảo mật -- **Localization**: Hỗ trợ đa ngôn ngữ (VI/EN) -- **Dark Mode**: Hỗ trợ giao diện tối +```mermaid +stateDiagram-v2 + [*] --> SplashScreen + SplashScreen --> CheckAuth: App Launch + CheckAuth --> Authenticated: Token Valid + CheckAuth --> Unauthenticated: No Token + Unauthenticated --> Login + Login --> Authenticated: Success + Login --> Register: Sign Up + Register --> Authenticated: Success + Authenticated --> HomeScreen + HomeScreen --> Unauthenticated: Logout +``` -## 📋 Conventions +### Data Flow -### Coding Style +``` +User Action → View → ViewModel.method() → Service.request() → API + ↓ + @Published update + ↓ + View rerender +``` +## 📋 Coding Conventions / Quy Ước Code + +### File Structure +```swift +// MARK: - Imports +import SwiftUI + +// MARK: - Type Definition +/// Description in English +/// Mô tả bằng tiếng Việt +struct/class/enum TypeName { + + // MARK: - Properties + + // MARK: - Init + + // MARK: - Public Methods + + // MARK: - Private Methods +} + +// MARK: - Extensions + +// MARK: - Preview Provider (DEBUG only) +``` + +### ViewModel Pattern ```swift -// ViewModel với ObservableObject @MainActor -class HomeViewModel: ObservableObject { - @Published var isLoading: Bool = false +final class FeatureViewModel: ObservableObject { + // Published properties for UI binding + @Published var isLoading = false + @Published var errorMessage: String? + @Published var data: [Model] = [] + // Dependencies via init + private let apiService: APIServiceProtocol + + init(apiService: APIServiceProtocol = APIService.shared) { + self.apiService = apiService + } + + // Async methods func loadData() async { - // Async data loading - } -} - -// View với MVVM binding -struct HomeView: View { - @StateObject private var viewModel = HomeViewModel() - - var body: some View { - // SwiftUI body + isLoading = true + defer { isLoading = false } + + do { + data = try await apiService.get(endpoint: "/data") + } catch { + errorMessage = error.localizedDescription + } } } ``` -### Comments (Bilingual) - +### Bilingual Comments ```swift -/// Load home screen data -/// Tải dữ liệu màn hình home -func loadData() async { } +/// Load user profile data +/// Tải dữ liệu hồ sơ người dùng +func loadProfile() async { } ``` -## 🔗 Liên kết +## ⚙️ Configuration / Cấu Hình -- [app-client-base-net](../app-client-base-net) - .NET MAUI client -- [web-client](../web-client) - Web client +### API Configuration +```swift +// Core/Constants/Constants.swift +enum APIConfig { + static let baseURL = "https://api.goodgo.vn" + static let apiVersion = "/api/v1" + static let timeout: TimeInterval = 30.0 +} +``` + +### Environment Variables +| Key | Description / Mô tả | Default | +|-----|---------------------|---------| +| `API_BASE_URL` | Backend API URL | `https://api.goodgo.vn` | +| `API_VERSION` | API version prefix | `/api/v1` | + +## 🧪 Testing / Kiểm Thử + +### Run Unit Tests +```bash +xcodebuild test \ + -project AppClientBaseSwift/AppClientBaseSwift.xcodeproj \ + -scheme AppClientBaseSwift \ + -destination 'platform=iOS Simulator,name=iPhone 15 Pro' +``` + +### Test Plan +Located at: `AppClientBaseSwift.xctestplan` + +## 🔐 Security / Bảo Mật + +| Feature | Implementation / Triển khai | +|---------|----------------------------| +| Token Storage | Keychain Services (not UserDefaults) | +| Secure Requests | HTTPS only, Bearer token auth | +| Session Management | Auto token refresh, secure logout | +| Data Protection | Sensitive data encrypted at rest | + +## 📱 Supported Devices / Thiết Bị Hỗ Trợ + +- **iPhone**: 8 and later (iOS 15+) +- **iPad**: All iPads with iOS 15+ +- **Orientations**: Portrait (primary), Landscape (supported) + +## 🔗 Related Projects / Dự Án Liên Quan + +- [app-client-base-net](../app-client-base-net) - .NET MAUI cross-platform client +- [iam-service-net](../../services/iam-service-net) - Authentication backend +- [web-client](../web-client) - Web application + +## 📚 Additional Documentation / Tài Liệu Bổ Sung + +- [ARCHITECTURE.md](./ARCHITECTURE.md) - Chi tiết kiến trúc và design decisions +- [Swift Enterprise Skills](../../.agent/skills/swift-enterprise-architect/SKILL.md) - Swift development guidelines ## 📄 License