docs: Thêm tài liệu kiến trúc ARCHITECTURE.md và cập nhật README.md.
This commit is contained in:
301
apps/app-client-base-swift/ARCHITECTURE.md
Normal file
301
apps/app-client-base-swift/ARCHITECTURE.md
Normal file
@@ -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<T>() │ │ │ │ - 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<T: Decodable>(
|
||||
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<T: Decodable>(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)
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
|
||||
@@ -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<T: Decodable>(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<T: Decodable>(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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
## <EFBFBD> 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user