Commit e838a036 authored by DatHV's avatar DatHV
Browse files

update config

parent 3a2a0642
# Giải thích chi tiết về Cấu trúc Mới
## Tổng quan
Cấu trúc mới được thiết kế theo **Clean Architecture** kết hợp với **Feature-based organization**, giúp code dễ maintain, scale và test hơn.
## Kiến trúc 3 Layers
### 1. Core Layer (`lib/core/`)
**Mục đích**: Chứa các thành phần được dùng chung trên toàn bộ app, không phụ thuộc vào business logic cụ thể.
#### `core/config/`
- **Chức năng**: Tất cả các configuration files
- **Files**:
- `api_paths.dart` - API endpoints
- `constants.dart` - App constants
- `device_info.dart` - Device information
- `env_loader.dart` - Environment loader
- **Lý do**: Tập trung tất cả configs ở một nơi, dễ quản lý và thay đổi
#### `core/initialization/`
- **Chức năng**: Khởi tạo app và các services
- **Files**:
- `app_initializer.dart` - Main app initialization
- `web_app_initializer.dart` - Web-specific initialization
- `deep_link_service.dart` - Deep linking
- **Lý do**: Tách biệt logic khởi tạo, dễ test và maintain
#### `core/network/`
- **Chức năng**: Network layer với HTTP client, interceptors, API clients
- **Cấu trúc**:
```
network/
├── api/ # API clients (affiliate, game, location, etc.)
├── interceptor/ # HTTP interceptors (auth, logger, error)
├── dio_http_service.dart
├── error_mapper.dart
└── restful_api_client.dart
```
- **Lý do**: Tập trung tất cả network logic, dễ mock khi test
#### `core/storage/`
- **Chức năng**: Local storage và preferences
- **Files**:
- `data_preference.dart` - SharedPreferences wrapper
- `contact_storage_service.dart` - Contact storage
- `point/` - Point management storage
- **Lý do**: Tách biệt storage logic, dễ thay đổi implementation
#### `core/services/`
- **Chức năng**: Core business services
- **Cấu trúc**:
```
services/
├── auth/
│ ├── login_service.dart
│ ├── logout_service.dart
│ └── token_refresh_service.dart
└── notification/
```
- **Lý do**: Services được dùng chung, không thuộc về feature cụ thể
#### `core/platform/`
- **Chức năng**: Platform-specific code
- **Cấu trúc**:
```
platform/
├── web/ # Web-specific (x-app-sdk, web helpers)
├── permission/ # Permissions (biometric, etc.)
└── firebase/ # Firebase integration
```
- **Lý do**: Tách biệt platform code, dễ maintain cho từng platform
#### `core/base/`
- **Chức năng**: Base classes cho screens, viewmodels, models
- **Files**:
- `base_screen.dart` - Base class cho screens
- `base_view_model.dart` - Base class cho viewmodels
- `base_response_model.dart` - Base class cho API responses
- `basic_state.dart` - Base state management
- **Lý do**: DRY principle, giảm code duplication
#### `core/utils/`
- **Chức năng**: Utilities và extensions
- **Cấu trúc**:
```
utils/
├── validation_utils.dart
├── direction_google_map.dart
├── router_gage.dart
└── extensions/ # Extension methods
├── collection_extension.dart
├── color_extension.dart
├── date_format.dart
└── ...
```
- **Lý do**: Gộp `shared/``utils/` lại, tránh confusion
#### `core/navigation/`
- **Chức năng**: Navigation và routing
- **Files**:
- `app_navigator.dart` - Main navigator
- `deferred_routes.dart` - Lazy loading routes
- `directional_action_type.dart` - Direction actions
- `directional_screen.dart` - Direction screens
- **Lý do**: Gộp `deferred/``directional/` lại, tất cả navigation ở một nơi
#### `core/theme/`
- **Chức năng**: Theme, colors, styles, images
- **Files**:
- `colors.dart` (từ `base_color.dart`)
- `text_styles.dart` (từ `text_style.dart`)
- `button_styles.dart` (từ `button_style.dart`)
- `images.dart` (từ `define_image.dart`)
- **Lý do**: Đổi tên từ `resources/` sang `theme/` cho rõ ràng hơn
#### `core/widgets/`
- **Chức năng**: Core reusable widgets
- **Files**:
- `app_loading.dart`
- `alert/` - Alert widgets
- `custom_app_bar.dart`
- `custom_empty_widget.dart`
- Và các widgets dùng chung khác
- **Lý do**: Widgets được dùng chung trên toàn app
---
### 2. Features Layer (`lib/features/`)
**Mục đích**: Mỗi feature là một module độc lập, có thể phát triển và test riêng biệt.
#### Cấu trúc mỗi Feature
Mỗi feature có 3 layers:
```
feature_name/
├── data/ # Data layer (outermost)
│ ├── models/ # Data models (API responses)
│ ├── repositories/ # Repository implementations
│ └── datasources/ # Remote/Local data sources
├── domain/ # Domain layer (middle)
│ ├── entities/ # Business entities
│ └── usecases/ # Use cases (business logic)
└── presentation/ # Presentation layer (innermost)
├── screens/ # UI screens
├── viewmodels/ # ViewModels (state management)
└── widgets/ # Feature-specific widgets
```
#### Ví dụ: Auth Feature
```
features/auth/
├── data/
│ ├── models/
│ │ ├── login_token_response_model.dart # API response model
│ │ ├── profile_response_model.dart
│ │ └── user_agreement_model.dart
│ ├── repositories/
│ │ └── auth_repository_impl.dart # Implements domain repository
│ └── datasources/
│ ├── auth_remote_datasource.dart # API calls
│ └── auth_local_datasource.dart # Local storage
├── domain/
│ ├── entities/
│ │ └── user.dart # Business entity (pure Dart)
│ ├── repositories/
│ │ └── auth_repository.dart # Repository interface
│ └── usecases/
│ ├── login_usecase.dart # Business logic
│ ├── logout_usecase.dart
│ └── get_profile_usecase.dart
└── presentation/
├── screens/
│ ├── login/
│ │ ├── login_screen.dart
│ │ └── login_viewmodel.dart
│ ├── otp/
│ └── create_pass/
└── widgets/
└── auth_form_widget.dart
```
**Lợi ích**:
- **Separation of concerns**: Mỗi layer có trách nhiệm riêng
- **Testability**: Có thể test từng layer độc lập
- **Dependency rule**: Inner layers không phụ thuộc outer layers
- **Reusability**: Domain layer có thể reuse cho web/mobile
#### Feature Organization
**1. Auth Feature** (`features/auth/`)
- Login, OTP, Create password
- Authentication logic
**2. Home Feature** (`features/home/`)
- Home screen (40 files)
- Dashboard, main content
**3. Voucher Feature** (`features/voucher/`)
- Voucher screens (46 files)
- Voucher management
**4. Transaction Feature** (`features/transaction/`)
- Transaction screens (25 files)
- Transaction history, details
**5. Campaign Feature** (`features/campaign/`)
- Campaign 7day
- Quiz campaign
- Register campaign
- Invite friend campaign
**6. Payment Feature** (`features/payment/`)
- Electric payment
- Mobile card
- Topup
- Traffic service
**7. Point Feature** (`features/point/`)
- History point
- History point cashback
- Daily checkin
**8. Profile Feature** (`features/profile/`)
- Personal
- Settings
- Change password
- Delete account
- Biometric
**9. Notification Feature** (`features/notification/`)
- Notification screens (12 files)
**10. Membership Feature** (`features/membership/`)
- Membership screens (10 files)
**11. Affiliate Feature** (`features/affiliate/`)
- Affiliate
- Affiliate brand detail
**12. Game Feature** (`features/game/`)
- Game
- VPlay game center
**13. Support Feature** (`features/support/`)
- Support
- FAQs
- News
**14. Common Feature** (`features/common/`)
- Splash
- Onboarding
- Webview
- QR code
- Popup manager
- Main tab screen
---
### 3. Shared Layer (`lib/shared/`)
**Mục đích**: Widgets và components được dùng chung giữa các features.
```
shared/
└── widgets/
├── custom_navigation_bar.dart
├── custom_point_text_tag.dart
└── ...
```
**Lý do**: Khác với `core/widgets/`, `shared/widgets/` là các widgets được dùng bởi nhiều features nhưng không phải core functionality.
---
## Dependency Flow
```
Presentation Layer (UI)
↓ depends on
Domain Layer (Business Logic)
↓ depends on
Data Layer (API, Storage)
```
**Quy tắc**:
- Presentation chỉ phụ thuộc Domain
- Domain không phụ thuộc Presentation hay Data
- Data phụ thuộc Domain (implements interfaces)
---
## So sánh với cấu trúc cũ
### Cấu trúc cũ:
```
lib/
├── screen/ # 375+ files trong 1 thư mục
├── model/ # Chỉ có auth models
├── networking/ # Network layer
├── services/ # Services rời rạc
├── shared/ # Không rõ mục đích
├── utils/ # Trùng với shared
└── ...
```
**Vấn đề**:
- Khó tìm code
- Khó maintain
- Khó test
- Khó scale
### Cấu trúc mới:
```
lib/
├── core/ # Core functionality
│ ├── config/
│ ├── network/
│ ├── storage/
│ └── ...
├── features/ # Feature modules
│ ├── auth/
│ │ ├── data/
│ │ ├── domain/
│ │ └── presentation/
│ └── ...
└── shared/ # Shared widgets
```
**Lợi ích**:
- ✅ Dễ tìm code (theo feature)
- ✅ Dễ maintain (mỗi feature độc lập)
- ✅ Dễ test (test từng layer)
- ✅ Dễ scale (thêm feature mới dễ dàng)
---
## Migration Strategy
### Phase 1: Core Reorganization (Low Risk)
- Gộp configs, utils, navigation
- Không ảnh hưởng business logic
- Dễ rollback
### Phase 2: Network & Services (Medium Risk)
- Di chuyển networking và services
- Cần update imports
- Test kỹ API calls
### Phase 3: Feature-based (High Risk)
- Di chuyển screens theo features
- Tạo data/domain/presentation layers
- Cần test toàn bộ app
---
## Best Practices
1. **Mỗi feature độc lập**: Có thể develop và test riêng
2. **Dependency injection**: Dùng GetX hoặc provider
3. **Repository pattern**: Tách biệt data source và business logic
4. **Use cases**: Mỗi business action là một use case
5. **Entities**: Pure Dart classes, không phụ thuộc framework
---
## Ví dụ Migration
### Trước (Cấu trúc cũ):
```dart
// lib/screen/login/login_screen.dart
import 'package:mypoint_flutter_app/networking/restful_api_client.dart';
import 'package:mypoint_flutter_app/model/auth/login_token_response_model.dart';
import 'package:mypoint_flutter_app/services/login_service.dart';
```
### Sau (Cấu trúc mới):
```dart
// lib/features/auth/presentation/screens/login/login_screen.dart
import 'package:mypoint_flutter_app/features/auth/domain/usecases/login_usecase.dart';
import 'package:mypoint_flutter_app/features/auth/domain/entities/user.dart';
```
**Lợi ích**:
- Presentation không phụ thuộc trực tiếp vào API
- Dễ test (mock use case)
- Dễ thay đổi implementation
---
## Kết luận
Cấu trúc mới giúp:
- ✅ Code organization tốt hơn
- ✅ Dễ maintain và scale
- ✅ Dễ test
- ✅ Team collaboration tốt hơn
- ✅ Tuân thủ Clean Architecture principles
# Close App Integration
## Tổng quan
Tính năng `closeApp` cho phép web app đóng ứng dụng và trả về Super App với dữ liệu tùy chọn.
## Cách sử dụng
### 1. Import cần thiết
```dart
import 'package:flutter/foundation.dart';
import 'package:mypoint_flutter_app/web/web_helper.dart';
```
### 2. Các cách gọi closeApp
#### Đóng app đơn giản (không trả về dữ liệu)
```dart
if (kIsWeb) {
webCloseApp();
}
```
#### Đóng app với dữ liệu thành công
```dart
if (kIsWeb) {
webCloseApp({
'result': 'success',
'message': 'Operation completed successfully',
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
}
```
#### Đóng app với dữ liệu lỗi
```dart
if (kIsWeb) {
webCloseApp({
'result': 'error',
'message': 'Something went wrong',
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
}
```
#### Đóng app với dữ liệu tùy chỉnh
```dart
if (kIsWeb) {
webCloseApp({
'result': 'custom',
'data': {
'userId': '12345',
'action': 'completed',
'metadata': {'key': 'value'}
},
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
}
```
### 3. Ví dụ thực tế
#### Sau khi thanh toán thành công
```dart
void onPaymentSuccess(String transactionId, double amount) {
if (kIsWeb) {
webCloseApp({
'result': 'payment_success',
'transactionId': transactionId,
'amount': amount,
'currency': 'VND',
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
}
}
```
#### Sau khi submit form
```dart
void onFormSubmitted(Map<String, dynamic> formData) {
if (kIsWeb) {
webCloseApp({
'result': 'form_submitted',
'formType': 'registration',
'formData': formData,
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
}
}
```
#### Khi người dùng hủy thao tác
```dart
void onUserCancel() {
if (kIsWeb) {
webCloseApp({
'result': 'cancelled',
'message': 'User cancelled the operation',
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
}
}
```
## Cấu trúc dữ liệu trả về
### Format chuẩn
```dart
{
'result': String, // 'success', 'error', 'cancelled', 'custom', etc.
'message': String?, // Thông báo mô tả (optional)
'data': dynamic?, // Dữ liệu tùy chỉnh (optional)
'timestamp': int, // Timestamp (milliseconds)
// ... các field khác tùy theo use case
}
```
### Ví dụ các loại result
- `success`: Thao tác thành công
- `error`: Có lỗi xảy ra
- `cancelled`: Người dùng hủy
- `payment_success`: Thanh toán thành công
- `form_submitted`: Form đã submit
- `custom`: Dữ liệu tùy chỉnh
## Fallback behavior
Nếu Super App không cung cấp `closeApp` function:
1. Sẽ thử `window.history.back()` để quay lại trang trước
2. Nếu không có history, sẽ thử `window.close()` để đóng tab/window
3. Log warning trong console
## Testing
### Test với URL parameters
```bash
# Mở app với dữ liệu test
http://localhost:8080/?token=test123&user={"id":"user123"}
```
### Test trong Super App
- Đảm bảo Super App đã implement `closeApp` function
- Kiểm tra console log để xem dữ liệu được trả về
## Lưu ý
1. **Chỉ hoạt động trên web**: Function `webCloseApp` chỉ hoạt động khi `kIsWeb = true`
2. **Kiểm tra platform**: Luôn kiểm tra `kIsWeb` trước khi gọi
3. **Error handling**: Function có built-in error handling và fallback
4. **Logging**: Tất cả hoạt động đều được log để debug
## Files liên quan
- `lib/web/web_helper_web.dart` - Implementation cho web
- `lib/web/web_helper_stub.dart` - Stub cho non-web platforms
- `web/index.html` - JavaScript implementation
- `lib/web/close_app_example.dart` - Ví dụ sử dụng
# Migration Guide - Chuyển đổi sang Cấu trúc Mới
## Tổng quan
Guide này hướng dẫn từng bước để migrate từ cấu trúc cũ sang cấu trúc mới theo Clean Architecture.
## ⚠️ Lưu ý quan trọng
1. **Backup trước khi bắt đầu**: Script sẽ tự động tạo backup, nhưng nên commit code trước
2. **Chạy từng phase**: Không chạy tất cả phases cùng lúc
3. **Test sau mỗi phase**: Đảm bảo app vẫn hoạt động sau mỗi bước
4. **Update imports**: Sau mỗi phase, cần update imports
## Prerequisites
```bash
# Đảm bảo đã commit code
git status
git add .
git commit -m "Before migration to new structure"
```
## Phase 1: Core Reorganization (Low Risk)
### Mục tiêu
Tổ chức lại core layer: configs, utils, navigation, theme
### Các thay đổi
- `configs/``core/config/`
- `env_loader.dart``core/config/env_loader.dart`
- `core/app_initializer.dart``core/initialization/app_initializer.dart`
- `utils/` + `shared/``core/utils/`
- `extensions/``core/utils/extensions/`
- `deferred/` + `directional/``core/navigation/`
- `resources/``core/theme/` (và rename files)
### Các bước
#### Bước 1: Chạy migration script
```bash
./migrate_structure.sh phase1
```
#### Bước 2: Update imports
```bash
./update_imports.sh phase1
```
#### Bước 3: Update file references
Cần update các references trong code:
**Resources/Theme:**
```dart
// Trước
import 'package:mypoint_flutter_app/resources/base_color.dart';
final color = BaseColor.primary;
// Sau
import 'package:mypoint_flutter_app/core/theme/colors.dart';
final color = AppColors.primary; // Cần rename class nếu cần
```
**Configs:**
```dart
// Trước
import 'package:mypoint_flutter_app/configs/api_paths.dart';
// Sau
import 'package:mypoint_flutter_app/core/config/api_paths.dart';
```
#### Bước 4: Test
```bash
flutter pub get
flutter analyze
flutter run
```
#### Bước 5: Fix lỗi
- Kiểm tra các lỗi import
- Update các references còn sót
- Test lại app
---
## Phase 2: Network & Services (Medium Risk)
### Mục tiêu
Tổ chức lại network layer, services, storage, và platform code
### Các thay đổi
- `networking/``core/network/`
- `services/``core/services/auth/`
- `preference/``core/storage/`
- `web/``core/platform/web/`
- `permission/``core/platform/permission/`
- `firebase/``core/platform/firebase/`
### Các bước
#### Bước 1: Chạy migration script
```bash
./migrate_structure.sh phase2
```
#### Bước 2: Update imports
```bash
./update_imports.sh phase2
```
#### Bước 3: Update API client references
```dart
// Trước
import 'package:mypoint_flutter_app/networking/api/affiliate_api.dart';
// Sau
import 'package:mypoint_flutter_app/core/network/api/affiliate_api.dart';
```
#### Bước 4: Update service references
```dart
// Trước
import 'package:mypoint_flutter_app/services/login_service.dart';
// Sau
import 'package:mypoint_flutter_app/core/services/auth/login_service.dart';
```
#### Bước 5: Update storage references
```dart
// Trước
import 'package:mypoint_flutter_app/preference/data_preference.dart';
// Sau
import 'package:mypoint_flutter_app/core/storage/data_preference.dart';
```
#### Bước 6: Update platform references
```dart
// Trước
import 'package:mypoint_flutter_app/web/web_helper.dart';
// Sau
import 'package:mypoint_flutter_app/core/platform/web/web_helper.dart';
```
#### Bước 7: Test
```bash
flutter pub get
flutter analyze
flutter run
# Test API calls, services, storage
```
---
## Phase 3: Feature-based Organization (High Risk)
### Mục tiêu
Tổ chức screens theo features với data/domain/presentation layers
### ⚠️ Cảnh báo
Phase này **KHÔNG tự động hoàn toàn**. Cần:
- Di chuyển screens thủ công
- Tạo data/domain layers
- Refactor code để tuân thủ Clean Architecture
### Các bước
#### Bước 1: Tạo feature structure
```bash
./migrate_structure.sh phase3
```
Script sẽ tạo cấu trúc thư mục cho tất cả features nhưng **KHÔNG di chuyển files**.
#### Bước 2: Di chuyển screens thủ công
**Ví dụ: Auth Feature**
```bash
# Di chuyển login screens
mkdir -p lib/features/auth/presentation/screens/login
mv lib/screen/login/* lib/features/auth/presentation/screens/login/
# Di chuyển OTP screens
mkdir -p lib/features/auth/presentation/screens/otp
mv lib/screen/otp/* lib/features/auth/presentation/screens/otp/
# Di chuyển create_pass screens
mkdir -p lib/features/auth/presentation/screens/create_pass
mv lib/screen/create_pass/* lib/features/auth/presentation/screens/create_pass/
```
**Ví dụ: Home Feature**
```bash
# Di chuyển home screens
mkdir -p lib/features/home/presentation/screens
mv lib/screen/home/* lib/features/home/presentation/screens/
```
#### Bước 3: Di chuyển models
```bash
# Di chuyển auth models
mv lib/model/auth/* lib/features/auth/data/models/
```
#### Bước 4: Update imports trong screens
Cần update imports trong các files đã di chuyển:
```dart
// Trước (trong login_screen.dart)
import 'package:mypoint_flutter_app/model/auth/login_token_response_model.dart';
import 'package:mypoint_flutter_app/services/login_service.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client.dart';
// Sau
import 'package:mypoint_flutter_app/features/auth/data/models/login_token_response_model.dart';
import 'package:mypoint_flutter_app/core/services/auth/login_service.dart';
import 'package:mypoint_flutter_app/core/network/restful_api_client.dart';
```
#### Bước 5: Tạo Domain Layer (Optional nhưng khuyến nghị)
**Tạo entities:**
```dart
// lib/features/auth/domain/entities/user.dart
class User {
final String id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
}
```
**Tạo use cases:**
```dart
// lib/features/auth/domain/usecases/login_usecase.dart
class LoginUseCase {
final AuthRepository repository;
LoginUseCase(this.repository);
Future<Either<Failure, User>> call(LoginParams params) async {
return await repository.login(params.email, params.password);
}
}
```
**Tạo repository interface:**
```dart
// lib/features/auth/domain/repositories/auth_repository.dart
abstract class AuthRepository {
Future<Either<Failure, User>> login(String email, String password);
Future<Either<Failure, void>> logout();
}
```
#### Bước 6: Tạo Data Layer
**Tạo repository implementation:**
```dart
// lib/features/auth/data/repositories/auth_repository_impl.dart
class AuthRepositoryImpl implements AuthRepository {
final AuthRemoteDataSource remoteDataSource;
final AuthLocalDataSource localDataSource;
@override
Future<Either<Failure, User>> login(String email, String password) async {
try {
final response = await remoteDataSource.login(email, password);
return Right(response.toEntity());
} catch (e) {
return Left(ServerFailure(e.toString()));
}
}
}
```
#### Bước 7: Update routes
Cần update route definitions:
```dart
// Trước
GetPage(
name: '/login',
page: () => LoginScreen(),
)
// Sau
GetPage(
name: '/login',
page: () => const LoginScreen(), // Import từ features/auth
)
```
#### Bước 8: Test toàn bộ app
```bash
flutter pub get
flutter analyze
flutter test
flutter run
# Test tất cả features
```
---
## Checklist Migration
### Phase 1 Checklist
- [ ] Backup created
- [ ] Migration script chạy thành công
- [ ] Imports updated
- [ ] `flutter pub get` chạy thành công
- [ ] `flutter analyze` không có lỗi
- [ ] App chạy được
- [ ] Test các chức năng cơ bản
### Phase 2 Checklist
- [ ] Migration script chạy thành công
- [ ] Imports updated
- [ ] API calls hoạt động
- [ ] Services hoạt động
- [ ] Storage hoạt động
- [ ] Platform code hoạt động
- [ ] `flutter analyze` không có lỗi
- [ ] App chạy được
### Phase 3 Checklist
- [ ] Feature structures created
- [ ] Screens đã di chuyển
- [ ] Models đã di chuyển
- [ ] Imports updated
- [ ] Routes updated
- [ ] Domain layer created (optional)
- [ ] Data layer created (optional)
- [ ] `flutter analyze` không có lỗi
- [ ] App chạy được
- [ ] Tất cả features test thành công
---
## Rollback Plan
Nếu có vấn đề, có thể rollback:
```bash
# Restore từ backup
cp -r .migration_backup_YYYYMMDD_HHMMSS/lib/* lib/
# Hoặc từ git
git reset --hard HEAD
```
---
## Common Issues & Solutions
### Issue 1: Import errors
**Solution**: Chạy `./update_imports.sh phaseX` và kiểm tra lại
### Issue 2: Missing files
**Solution**: Kiểm tra backup folder, restore files cần thiết
### Issue 3: Circular dependencies
**Solution**: Refactor code để tránh circular imports
### Issue 4: Build errors
**Solution**:
```bash
flutter clean
flutter pub get
flutter pub upgrade
```
---
## Best Practices
1. **Commit sau mỗi phase**: Để dễ rollback nếu cần
2. **Test kỹ**: Test tất cả features sau mỗi phase
3. **Code review**: Review code sau mỗi phase
4. **Documentation**: Update documentation khi cần
5. **Team communication**: Thông báo team về changes
---
## Timeline ước tính
- **Phase 1**: 1-2 giờ (low risk)
- **Phase 2**: 2-4 giờ (medium risk)
- **Phase 3**: 1-2 tuần (high risk, cần refactor nhiều)
---
## Support
Nếu gặp vấn đề:
1. Kiểm tra backup folder
2. Review migration logs
3. Check import errors với `flutter analyze`
4. Rollback nếu cần
# Cấu trúc thư mục Project
Tài liệu này mô tả chi tiết cấu trúc thư mục của MyPoint Flutter App.
```
flutter_app_mypoint/
├── lib/ # Source code chính
│ ├── main.dart # Entry point
│ ├── env_loader.dart # Environment loader
│ │
│ ├── base/ # Base classes và utilities
│ │ ├── app_loading.dart
│ │ ├── app_navigator.dart
│ │ ├── base_response_model.dart
│ │ ├── base_screen.dart
│ │ ├── base_view_model.dart
│ │ └── basic_state.dart
│ │
│ ├── core/ # Core initialization
│ │ ├── app_initializer.dart
│ │ ├── web_app_initializer.dart
│ │ └── deep_link_service.dart
│ │
│ ├── configs/ # Configuration
│ │ ├── api_paths.dart
│ │ ├── callbacks.dart
│ │ ├── constants.dart
│ │ └── device_info.dart
│ │
│ ├── extensions/ # Extension methods
│ │ ├── collection_extension.dart
│ │ ├── color_extension.dart
│ │ ├── crypto.dart
│ │ ├── date_format.dart
│ │ ├── datetime_extensions.dart
│ │ ├── debouncer.dart
│ │ ├── num_extension.dart
│ │ └── string_extension.dart
│ │
│ ├── firebase/ # Firebase integration
│ │ ├── firebase_options.dart
│ │ ├── notification_parse_payload.dart
│ │ ├── push_notification.dart
│ │ ├── push_setup.dart
│ │ └── push_token_service.dart
│ │
│ ├── model/ # Data models
│ │ └── auth/ # Authentication models
│ │ ├── biometric_register_response_model.dart
│ │ ├── customer_balance_model.dart
│ │ ├── login_token_response_model.dart
│ │ ├── profile_response_model.dart
│ │ ├── user_agreement_model.dart
│ │ ├── worker_site_model.dart
│ │ └── working_site_model.dart
│ │
│ ├── networking/ # Network layer
│ │ ├── api/ # API clients
│ │ │ ├── affiliate_api.dart
│ │ │ ├── game_api.dart
│ │ │ ├── location_api.dart
│ │ │ ├── notification_api.dart
│ │ │ ├── product_api.dart
│ │ │ └── website_api.dart
│ │ ├── interceptor/ # HTTP interceptors
│ │ │ ├── auth_interceptor.dart
│ │ │ ├── exception_interceptor.dart
│ │ │ ├── logger_interceptor.dart
│ │ │ ├── network_error_gate.dart
│ │ │ └── request_interceptor.dart
│ │ ├── dio_extra_keys.dart
│ │ ├── dio_http_service.dart
│ │ ├── error_mapper.dart
│ │ ├── restful_api_client.dart
│ │ ├── restful_api_client_all_request.dart
│ │ └── restful_api_viewmodel.dart
│ │
│ ├── permission/ # Permissions
│ │ └── biometric_manager.dart
│ │
│ ├── preference/ # Local storage
│ │ ├── contact_storage_service.dart
│ │ ├── data_preference.dart
│ │ ├── package_info.dart
│ │ └── point/ # Point management
│ │ ├── header_home_model.dart
│ │ ├── header_home_model.g.dart
│ │ └── point_manager.dart
│ │
│ ├── resources/ # Resources
│ │ ├── base_color.dart
│ │ ├── button_style.dart
│ │ ├── define_image.dart
│ │ └── text_style.dart
│ │
│ ├── screen/ # UI Screens (375+ files)
│ │ ├── achievement/
│ │ ├── affiliate/
│ │ ├── affiliate_brand_detail/
│ │ ├── bank_account_manager/
│ │ ├── biometric/
│ │ ├── campaign7day/
│ │ ├── change_pass/
│ │ ├── contacts/
│ │ ├── create_pass/
│ │ ├── daily_checkin/
│ │ ├── data_network_service/
│ │ ├── delete_account/
│ │ ├── device_manager/
│ │ ├── electric_payment/
│ │ ├── faqs/
│ │ ├── flash_sale/
│ │ ├── game/
│ │ ├── health_book/
│ │ ├── history_point/
│ │ ├── history_point_cashback/
│ │ ├── home/ # Home screen (40 files)
│ │ ├── interested_categories/
│ │ ├── invite_friend_campaign/
│ │ ├── location_address/
│ │ ├── login/
│ │ ├── main_tab_screen/
│ │ ├── membership/
│ │ ├── mobile_card/
│ │ ├── news/
│ │ ├── notification/
│ │ ├── onboarding/
│ │ ├── order_menu/
│ │ ├── otp/
│ │ ├── pageDetail/
│ │ ├── personal/
│ │ ├── pipi/
│ │ ├── popup_manager/
│ │ ├── qr_code/
│ │ ├── quiz_campaign/
│ │ ├── register_campaign/
│ │ ├── setting/
│ │ ├── splash/
│ │ ├── support/
│ │ ├── topup/
│ │ ├── traffic_service/
│ │ ├── transaction/
│ │ ├── voucher/ # Voucher screens (46 files)
│ │ ├── vplay_game_center/
│ │ └── webview/
│ │
│ ├── services/ # Business services
│ │ ├── login_service.dart
│ │ ├── logout_service.dart
│ │ └── token_refresh_service.dart
│ │
│ ├── shared/ # Shared utilities
│ │ ├── direction_google_map.dart
│ │ └── router_gage.dart
│ │
│ ├── utils/ # Utilities
│ │ └── validation_utils.dart
│ │
│ ├── web/ # Web integration
│ │ ├── x_app_sdk_service.dart
│ │ ├── web_helper.dart
│ │ ├── web_helper_web.dart
│ │ └── web_helper_stub.dart
│ │
│ ├── widgets/ # Reusable widgets (22 files)
│ │ ├── alert/ # Alert widgets (6 files)
│ │ ├── back_button.dart
│ │ ├── bottom_sheet_helper.dart
│ │ ├── button_container.dart
│ │ ├── custom_app_bar.dart
│ │ ├── custom_empty_widget.dart
│ │ ├── custom_navigation_bar.dart
│ │ ├── custom_point_text_tag.dart
│ │ ├── custom_price_tag.dart
│ │ ├── custom_search_navigation_bar.dart
│ │ ├── custom_toast_message.dart
│ │ ├── dashed_line.dart
│ │ ├── image_loader.dart
│ │ ├── measure_size.dart
│ │ ├── network_image_with_aspect_ratio.dart
│ │ ├── support_button.dart
│ │ └── time_picker_widget.dart
│ │
│ ├── deferred/ # Deferred loading
│ │ └── deferred_routes.dart
│ │
│ └── directional/ # Direction handling
│ ├── directional_action_type.dart
│ └── directional_screen.dart
├── assets/ # Assets
│ ├── config/ # Environment configs
│ │ ├── env.json # Active config (auto-generated)
│ │ └── env_dev.json # Development config
│ ├── data/ # Static data
│ │ ├── main_layout_section_home.json
│ │ └── support_data.json
│ ├── icons/ # App icons by environment
│ │ ├── dev/ # Development icons
│ │ ├── pro/ # Production icons
│ │ └── stg/ # Staging icons
│ └── images/ # Images (96 files)
│ ├── cashback/ # Cashback category icons
│ ├── splash_screen.png
│ ├── splash_screen.webp
│ └── ... (93 PNG files)
├── web/ # Web assets
│ ├── index.html # Main HTML file
│ ├── manifest.json # PWA manifest
│ ├── favicon.png
│ ├── icons/ # PWA icons
│ │ ├── Icon-192.png
│ │ ├── Icon-512.png
│ │ ├── Icon-maskable-192.png
│ │ └── Icon-maskable-512.png
│ ├── js/ # JavaScript files
│ │ └── x_app_sdk_loader.js
│ └── assets/
│ └── index.html
├── android/ # Android platform
│ ├── app/
│ │ ├── build.gradle.kts
│ │ ├── google-services.json
│ │ ├── proguard-rules.pro
│ │ ├── keystores/ # Signing keys
│ │ └── src/ # Android source (57 files)
│ ├── build.gradle.kts
│ ├── gradle/
│ │ └── wrapper/
│ ├── gradle.properties
│ ├── gradlew # Gradle wrapper
│ ├── key.properties
│ └── settings.gradle.kts
├── ios/ # iOS platform
│ ├── Flutter/ # Flutter configs
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ ├── Dev.xcconfig
│ │ ├── Pro.xcconfig
│ │ ├── Release.xcconfig
│ │ ├── Stg.xcconfig
│ │ └── Generated.xcconfig
│ ├── NotificationServices/ # Notification service extension
│ │ ├── Info.plist
│ │ └── NotificationService.swift
│ ├── Runner/ # iOS app
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets/ # iOS assets (43 files)
│ │ ├── Base.lproj/ # Storyboards
│ │ ├── Env/ # Environment configs (3 files)
│ │ ├── GoogleService-Info.plist
│ │ ├── Info.plist
│ │ └── Runner-Bridging-Header.h
│ ├── Runner.xcodeproj/ # Xcode project
│ ├── Runner.xcworkspace/ # Xcode workspace
│ ├── RunnerTests/ # iOS tests
│ ├── Podfile # CocoaPods dependencies
│ └── Pods/ # CocoaPods packages
├── test/ # Tests
│ └── widget_test.dart
├── tools/ # Development tools
│ └── print_size_info.dart
├── deploy/ # Deployment configs
│ └── nginx/ # Nginx configs
├── build/ # Build output (generated)
│ ├── web/ # Web build output
│ └── ios/ # iOS build artifacts
├── node_modules/ # Node.js dependencies
│ └── x-app-sdk/ # X-App-SDK package
├── web_dev_export_*/ # Development exports (generated)
│ └── serve_local.sh # Local server script
├── web_prod_export_*/ # Production exports (generated)
│ └── serve_local.sh # Local server script
├── export_dev.sh # Export development script
├── export_prod.sh # Export production script
├── run_dev.sh # Run development script
├── run_prod.sh # Run production preview script
├── run_dev_nginx.sh # Build + Nginx setup (all-in-one)
├── pubspec.yaml # Flutter dependencies
├── pubspec.lock # Flutter lock file
├── package.json # Node.js dependencies
├── package-lock.json # Node.js lock file
├── analysis_options.yaml # Dart analyzer config
├── firebase.json # Firebase config
├── devtools_options.yaml # DevTools config
├── README.md # Main documentation
└── PROJECT_STRUCTURE.md # This file
```
## Mô tả các thư mục chính
### `lib/`
Chứa toàn bộ source code Dart của ứng dụng, được tổ chức theo kiến trúc clean architecture:
- **base/**: Base classes và utilities dùng chung
- **core/**: Core initialization logic
- **configs/**: Configuration files
- **extensions/**: Extension methods cho các types
- **firebase/**: Firebase integration
- **model/**: Data models
- **networking/**: Network layer với API clients và interceptors
- **permission/**: Permission handling
- **preference/**: Local storage và preferences
- **resources/**: Resources như colors, styles, images
- **screen/**: UI screens (375+ files)
- **services/**: Business services
- **shared/**: Shared utilities
- **utils/**: Utility functions
- **web/**: Web-specific integration
- **widgets/**: Reusable widgets
- **deferred/**: Deferred loading
- **directional/**: Direction handling
### `assets/`
Chứa các assets tĩnh:
- **config/**: Environment configuration files
- **data/**: Static JSON data
- **icons/**: App icons theo từng environment (dev/pro/stg)
- **images/**: Images và icons (96 files)
### `web/`
Web assets cho Flutter web:
- **index.html**: Main HTML file
- **manifest.json**: PWA manifest
- **icons/**: PWA icons
- **js/**: JavaScript files (x-app-sdk loader)
### `android/` và `ios/`
Platform-specific code và configurations cho Android và iOS.
### `build/`
Thư mục build output (generated, không commit vào git).
### `web_dev_export_*/` và `web_prod_export_*/`
Thư mục export được tạo bởi `export_dev.sh``export_prod.sh`, chứa:
- Build output đã optimized
- Pre-compressed assets (.gz, .br)
- `serve_local.sh` script để test local
### Scripts
- `export_dev.sh`: Export development build
- `export_prod.sh`: Export production build
- `run_dev.sh`: Run development với CORS disabled
- `run_prod.sh`: Run production preview
- `run_dev_nginx.sh`: Build + Nginx setup (all-in-one)
# Đề xuất Cấu trúc Project (Refactored)
## Phân tích vấn đề hiện tại
### Vấn đề:
1. **Thiếu tổ chức theo layer**: Không rõ ràng về Presentation, Domain, Data layers
2. **Thư mục rời rạc**: `shared/`, `utils/`, `deferred/`, `directional/` không rõ mục đích
3. **Model chỉ có auth**: Thiếu models cho các features khác
4. **Screen quá lớn**: 375+ files trong một thư mục, khó quản lý
5. **Services và Networking tách rời**: Không có mối liên kết rõ ràng
6. **Thiếu domain layer**: Không có entities, use cases, repositories pattern
## Cấu trúc đề xuất (Clean Architecture + Feature-based)
```
flutter_app_mypoint/
├── lib/
│ ├── main.dart # Entry point
│ │
│ ├── core/ # Core functionality (shared across features)
│ │ ├── config/ # Configuration
│ │ │ ├── api_paths.dart
│ │ │ ├── constants.dart
│ │ │ ├── device_info.dart
│ │ │ └── env_loader.dart
│ │ │
│ │ ├── initialization/ # App initialization
│ │ │ ├── app_initializer.dart
│ │ │ ├── web_app_initializer.dart
│ │ │ └── deep_link_service.dart
│ │ │
│ │ ├── network/ # Network layer
│ │ │ ├── api/ # API clients
│ │ │ │ ├── affiliate_api.dart
│ │ │ │ ├── game_api.dart
│ │ │ │ ├── location_api.dart
│ │ │ │ ├── notification_api.dart
│ │ │ │ ├── product_api.dart
│ │ │ │ └── website_api.dart
│ │ │ ├── interceptor/ # HTTP interceptors
│ │ │ │ ├── auth_interceptor.dart
│ │ │ │ ├── exception_interceptor.dart
│ │ │ │ ├── logger_interceptor.dart
│ │ │ │ ├── network_error_gate.dart
│ │ │ │ └── request_interceptor.dart
│ │ │ ├── dio_http_service.dart
│ │ │ ├── error_mapper.dart
│ │ │ └── restful_api_client.dart
│ │ │
│ │ ├── storage/ # Local storage
│ │ │ ├── data_preference.dart
│ │ │ ├── contact_storage_service.dart
│ │ │ └── point/
│ │ │ ├── point_manager.dart
│ │ │ └── header_home_model.dart
│ │ │
│ │ ├── services/ # Core services
│ │ │ ├── auth/
│ │ │ │ ├── login_service.dart
│ │ │ │ ├── logout_service.dart
│ │ │ │ └── token_refresh_service.dart
│ │ │ └── notification/
│ │ │ └── (notification services)
│ │ │
│ │ ├── platform/ # Platform-specific
│ │ │ ├── web/
│ │ │ │ ├── x_app_sdk_service.dart
│ │ │ │ ├── web_helper.dart
│ │ │ │ └── web_helper_web.dart
│ │ │ ├── permission/
│ │ │ │ └── biometric_manager.dart
│ │ │ └── firebase/
│ │ │ ├── firebase_options.dart
│ │ │ ├── push_notification.dart
│ │ │ └── push_setup.dart
│ │ │
│ │ ├── base/ # Base classes
│ │ │ ├── base_screen.dart
│ │ │ ├── base_view_model.dart
│ │ │ ├── base_response_model.dart
│ │ │ └── basic_state.dart
│ │ │
│ │ ├── utils/ # Utilities (merged shared + utils)
│ │ │ ├── validation_utils.dart
│ │ │ ├── direction_google_map.dart
│ │ │ ├── router_gage.dart
│ │ │ └── extensions/ # Extension methods
│ │ │ ├── collection_extension.dart
│ │ │ ├── color_extension.dart
│ │ │ ├── crypto.dart
│ │ │ ├── date_format.dart
│ │ │ ├── datetime_extensions.dart
│ │ │ ├── debouncer.dart
│ │ │ ├── num_extension.dart
│ │ │ └── string_extension.dart
│ │ │
│ │ ├── navigation/ # Navigation (merged deferred + directional)
│ │ │ ├── app_navigator.dart
│ │ │ ├── deferred_routes.dart
│ │ │ ├── directional_action_type.dart
│ │ │ └── directional_screen.dart
│ │ │
│ │ ├── theme/ # Theme & Resources (renamed from resources)
│ │ │ ├── colors.dart # base_color.dart
│ │ │ ├── text_styles.dart # text_style.dart
│ │ │ ├── button_styles.dart # button_style.dart
│ │ │ └── images.dart # define_image.dart
│ │ │
│ │ └── widgets/ # Core reusable widgets
│ │ ├── app_loading.dart
│ │ ├── alert/ # Alert widgets
│ │ ├── custom_app_bar.dart
│ │ ├── custom_empty_widget.dart
│ │ └── ... (other core widgets)
│ │
│ ├── features/ # Feature modules (organized by domain)
│ │ │
│ │ ├── auth/ # Authentication feature
│ │ │ ├── data/
│ │ │ │ ├── models/ # Data models
│ │ │ │ │ ├── login_token_response_model.dart
│ │ │ │ │ ├── profile_response_model.dart
│ │ │ │ │ └── ...
│ │ │ │ ├── repositories/ # Data repositories
│ │ │ │ │ └── auth_repository.dart
│ │ │ │ └── datasources/ # Data sources
│ │ │ │ └── auth_remote_datasource.dart
│ │ │ ├── domain/
│ │ │ │ ├── entities/ # Domain entities
│ │ │ │ │ └── user.dart
│ │ │ │ └── usecases/ # Use cases
│ │ │ │ ├── login_usecase.dart
│ │ │ │ └── logout_usecase.dart
│ │ │ └── presentation/
│ │ │ ├── screens/
│ │ │ │ ├── login/
│ │ │ │ ├── otp/
│ │ │ │ └── create_pass/
│ │ │ ├── viewmodels/
│ │ │ └── widgets/ # Feature-specific widgets
│ │ │
│ │ ├── home/ # Home feature
│ │ │ ├── data/
│ │ │ │ ├── models/
│ │ │ │ └── repositories/
│ │ │ ├── domain/
│ │ │ │ ├── entities/
│ │ │ │ └── usecases/
│ │ │ └── presentation/
│ │ │ ├── screens/
│ │ │ │ └── (40 files from home/)
│ │ │ ├── viewmodels/
│ │ │ └── widgets/
│ │ │
│ │ ├── voucher/ # Voucher feature
│ │ │ ├── data/
│ │ │ ├── domain/
│ │ │ └── presentation/
│ │ │ └── screens/ # (46 files from voucher/)
│ │ │
│ │ ├── transaction/ # Transaction feature
│ │ │ ├── data/
│ │ │ ├── domain/
│ │ │ └── presentation/
│ │ │ └── screens/ # (25 files from transaction/)
│ │ │
│ │ ├── campaign/ # Campaign features (merged)
│ │ │ ├── campaign7day/
│ │ │ ├── quiz_campaign/
│ │ │ ├── register_campaign/
│ │ │ └── invite_friend_campaign/
│ │ │
│ │ ├── payment/ # Payment features (merged)
│ │ │ ├── electric_payment/
│ │ │ ├── mobile_card/
│ │ │ ├── topup/
│ │ │ └── traffic_service/
│ │ │
│ │ ├── point/ # Point management
│ │ │ ├── history_point/
│ │ │ ├── history_point_cashback/
│ │ │ └── daily_checkin/
│ │ │
│ │ ├── profile/ # Profile & Settings
│ │ │ ├── personal/
│ │ │ ├── setting/
│ │ │ ├── change_pass/
│ │ │ ├── delete_account/
│ │ │ └── biometric/
│ │ │
│ │ ├── notification/ # Notification feature
│ │ │ └── presentation/
│ │ │ └── screens/ # (12 files from notification/)
│ │ │
│ │ ├── membership/ # Membership feature
│ │ │ └── presentation/
│ │ │ └── screens/ # (10 files from membership/)
│ │ │
│ │ ├── affiliate/ # Affiliate feature
│ │ │ ├── affiliate/
│ │ │ └── affiliate_brand_detail/
│ │ │
│ │ ├── game/ # Game features
│ │ │ ├── game/
│ │ │ └── vplay_game_center/
│ │ │
│ │ ├── support/ # Support features
│ │ │ ├── support/
│ │ │ ├── faqs/
│ │ │ └── news/
│ │ │
│ │ └── common/ # Common features
│ │ ├── splash/
│ │ ├── onboarding/
│ │ ├── webview/
│ │ ├── qr_code/
│ │ ├── popup_manager/
│ │ └── main_tab_screen/
│ │
│ └── shared/ # Shared across features (optional)
│ └── widgets/ # Shared widgets
│ ├── custom_navigation_bar.dart
│ ├── custom_point_text_tag.dart
│ └── ...
├── assets/ # (unchanged)
├── web/ # (unchanged)
├── android/ # (unchanged)
├── ios/ # (unchanged)
└── ... (other files unchanged)
```
## So sánh với cấu trúc hiện tại
### Cải thiện chính:
1. **Core layer rõ ràng**:
- Gộp `configs/` vào `core/config/`
- Gộp `shared/``utils/` vào `core/utils/`
- Gộp `deferred/``directional/` vào `core/navigation/`
- Đổi `resources/` thành `core/theme/` (rõ ràng hơn)
2. **Feature-based organization**:
- Chia `screen/` thành các feature modules
- Mỗi feature có cấu trúc: `data/`, `domain/`, `presentation/`
- Dễ scale và maintain
3. **Separation of concerns**:
- **Data layer**: Models, repositories, datasources
- **Domain layer**: Entities, use cases
- **Presentation layer**: Screens, viewmodels, widgets
4. **Grouping related features**:
- Campaign features → `campaign/`
- Payment features → `payment/`
- Point features → `point/`
- Profile features → `profile/`
## Migration Plan
### Phase 1: Reorganize Core (Low risk)
1. Gộp `configs/``core/config/`
2. Gộp `shared/` + `utils/``core/utils/`
3. Gộp `deferred/` + `directional/``core/navigation/`
4. Đổi `resources/``core/theme/`
### Phase 2: Reorganize Network & Services (Medium risk)
1. Di chuyển `networking/``core/network/`
2. Di chuyển `services/``core/services/`
3. Di chuyển `preference/``core/storage/`
### Phase 3: Feature-based organization (High risk, cần test kỹ)
1. Tạo structure cho từng feature
2. Di chuyển screens theo feature
3. Tạo data/domain/presentation layers
## Lợi ích
1. **Scalability**: Dễ thêm features mới
2. **Maintainability**: Code được tổ chức rõ ràng
3. **Testability**: Dễ test từng layer riêng biệt
4. **Team collaboration**: Nhiều người có thể làm việc trên các features khác nhau
5. **Code reuse**: Core layer có thể reuse cho nhiều features
## Lưu ý
- Migration cần làm từng bước, test kỹ sau mỗi bước
- Có thể giữ cấu trúc cũ song song trong thời gian transition
- Update imports và dependencies sau mỗi bước di chuyển
This diff is collapsed.
# MyPoint Flutter App - Simple Commands
## 🚀 **Lệnh chính:**
### **Development (chạy local):**
```bash
./run_dev.sh
```
### **Export Development (để test trên server):**
```bash
./export_dev.sh
```
### **Export Production:**
```bash
./run_prod.sh
```
## 🔧 **Lệnh phụ:**
### **Chuyển đổi môi trường:**
```bash
# Development
./scripts/switch_env.sh dev
# Production
./scripts/switch_env.sh prod
```
### **Chạy web với CORS fix:**
```bash
./scripts/run_web_complete.sh
./scripts/open_browser_cors_disabled.sh
```
## 📁 **Cấu trúc đơn giản:**
```
flutter_app_mypoint/
├── run_dev.sh # Chạy development
├── run_prod.sh # Export production
├── export_web.sh # Export web app
├── assets/config/
│ ├── env.json # Config chính
│ └── env_dev.json # Config dev
├── scripts/
│ ├── switch_env.sh # Chuyển đổi môi trường
│ ├── run_web_complete.sh # Chạy web với CORS
│ ├── open_browser_cors_disabled.sh # Mở Chrome với CORS disabled
└── test_web.sh # Test web
└── lib/web/ # Web integration
```
## ✅ **Kết quả:**
-**Đơn giản**: Chỉ 2 lệnh chính
-**Dễ nhớ**: `./run_dev.sh``./run_prod.sh`
-**Web**: Tích hợp đầy đủ
-**Sẵn sàng**: Chạy ngay
# Simple Setup Guide
## ✅ Đã dọn dẹp xong!
### 🗑️ **Đã xóa:**
- Tất cả config môi trường phức tạp (`env_web_dev.json`, `env_web_stg.json`, `env_web_prod.json`)
- Các script export phức tạp (`export_web_dev.sh`, `export_web_stg.sh`, `export_web_prod.sh`)
- Script export tất cả môi trường (`export_all_envs.sh`)
- File hướng dẫn phức tạp (`ENVIRONMENT_CONFIG.md`)
### ✅ **Còn lại (đơn giản):**
#### **1. Config duy nhất:**
- `assets/config/env.json` - Config chính cho tất cả môi trường
#### **2. Scripts chính:**
- `./export_web.sh` - Export web app (đơn giản)
- `./scripts/run_web_complete.sh` - Chạy development với CORS
- `./scripts/export_and_run.sh` - Export + chạy + mở browser
## 🚀 **Cách sử dụng đơn giản:**
### **Development (chạy local):**
```bash
./run_dev.sh
```
### **Export Development (để test trên server):**
```bash
./export_dev.sh
```
### **Export Production:**
```bash
./run_prod.sh
```
### **Chuyển đổi môi trường thủ công:**
```bash
# Chuyển sang Development
./scripts/switch_env.sh dev
# Chuyển sang Production
./scripts/switch_env.sh prod
```
## ⚠️ **Vấn đề CORS:**
### **Lỗi thường gặp:**
```
Access to XMLHttpRequest at 'https://api.sandbox.mypoint.com.vn/...'
from origin 'http://localhost:8080' has been blocked by CORS policy
```
### **Nguyên nhân:**
- API server chỉ cho phép origin `https://api.evnpoint.com`
- Web app chạy trên `http://localhost:8080`
- Đây là vấn đề **server-side**, không phải client-side
### **Giải pháp:**
1. **Chrome với CORS disabled** (Khuyến nghị):
```bash
./scripts/open_chrome_cors_disabled.sh
```
2. **Script tự động**:
```bash
./scripts/run_web_cors_fixed.sh
```
3. **CORS Browser Extension**:
- Cài "CORS Unblock" extension
- Enable khi test
## 📁 **Cấu trúc đơn giản:**
```
flutter_app_mypoint/
├── assets/config/
│ └── env.json # Config duy nhất
├── lib/web/
│ └── (web helper files)
├── web/
│ └── index.html # JavaScript integration
├── export_web.sh # Export script
└── scripts/
├── run_web_complete.sh # Development
├── export_and_run.sh # Export + run
└── test_web.sh # Test web
```
## 🎯 **Kết quả:**
-**Đơn giản**: Chỉ 1 config file
-**Dễ hiểu**: Không còn phức tạp
-**Web**: Tích hợp đầy đủ
-**Sẵn sàng**: Export và deploy ngay
# X-App-SDK Integration Guide
## Tổng quan
Tài liệu này mô tả cách tích hợp mini app với `x-app-sdk` và cách Flutter web wrapper (`XAppSDKService`) expose đầy đủ API như `getToken()`, `closeApp()`, `configUIApp()` cùng các tiện ích khác từ Super App.
## Cài đặt
### 1. Install x-app-sdk
```bash
npm install x-app-sdk@^1.1.2
```
### 2. Build Flutter web app
```bash
flutter build web
```
## Cách sử dụng
### Trong Super App
Super App không cần làm gì đặc biệt. x-app-sdk sẽ tự động detect Super App environment và sử dụng các method có sẵn.
**Lưu ý**: Mini app sử dụng x-app-sdk thật từ npm package, không phải mock.
### Trong Mini App (Flutter)
Mini app sẽ tự động lấy token từ Super App khi khởi động:
```dart
// Token được lấy tự động trong splash screen
// Không cần gọi thủ công
// Đóng app và trả về Super App
webCloseApp({
'message': 'Task completed',
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
```
## API Reference
### Web Helper Functions
**Init & Diagnostics**
- `webInitializeXAppSDK()`: Khởi tạo x-app-sdk service
- `webIsSDKInitialized()`: Kiểm tra SDK đã khởi tạo chưa
- `webGetToken()`: Lấy token từ Super App
- `webGetCachedToken()`: Lấy token đã cache
- `webGetLastError()`: Lấy error message cuối cùng
- `webClearTokenCache()`: Xóa token cache
- `webResetSDK()`: Reset SDK service
- `webCloseApp(data) -> bool`: Đóng app, trả về `true` nếu host xử lý thành công; `false` khi chạy browser mode (dùng fallback điều hướng tại Flutter)
**Config**
- `webConfigUIApp(config)`: Thiết lập UI trong Super App
```dart
import 'package:flutter/foundation.dart';
import 'package:mypoint_flutter_app/web/web_helper.dart';
Future<void> configureHeader() async {
if (!webIsSDKInitialized()) {
await webInitializeXAppSDK();
}
final response = await webConfigUIApp({
'headerTitle': 'Tên ứng dụng',
'headerColor': '#ffffff',
'headerTextColor': '#000000',
});
if (response != null) {
debugPrint('Cấu hình thành công: $response');
} else {
debugPrint('Cấu hình thất bại: ${webGetLastError()}');
}
}
```
**Device**
- `webCallPhone(phone)` / `webCall(phone)`: Gọi điện
- `webSendSms(phone)` / `webSms(phone)`: Mở app SMS
- `webVibrate()`: Rung thiết bị
**Location**
- `webCurrentLocation()`: Lấy vị trí hiện tại
- `webRequestLocationPermission()`: Xin quyền vị trí
**Media**
- `webOpenPickerImage(type)`: Mở image picker
- `webOpenPickerFile([options])`: Mở file picker
**Notification & Payment**
- `webListenNotificationEvent(onEvent)`: Lắng nghe notification
- `webPaymentRequest(payload)`: Gửi yêu cầu thanh toán
- `webListenPaymentEvent(onEvent)`: Lắng nghe sự kiện thanh toán
**Permission**
- `webPermissionsRequest(type)` / `webPremissionsRequest(type)`: Xin quyền theo SDK
**Store**
- `webSaveStore(data)`: Lưu dữ liệu
- `webGetStore()`: Lấy dữ liệu
- `webClearStore()`: Xóa dữ liệu
**User**
- `webGetInfo(key)`: Lấy thông tin user
### XAppSDKService
```dart
final service = XAppSDKService();
// Khởi tạo
await service.initialize();
// Lấy token
String? token = await service.getToken();
// Đóng app
final closed = await service.closeApp({'message': 'Done'});
if (!closed) {
// Không có Super App host => fallback
Navigator.of(context).pushReplacementNamed(onboardingRoute);
}
// Kiểm tra trạng thái
bool isReady = service.isInitialized;
String? cachedToken = service.cachedToken;
String? error = service.lastError;
// Ví dụ lắng nghe notification
final removeNotification = await service.listenNotificationEvent((event) {
debugPrint('Notification event: $event');
});
```
## Luồng hoạt động
1. **Khởi tạo**: App khởi tạo x-app-sdk khi start
2. **Splash Screen**: Tự động gọi `getToken()` để lấy token
3. **Fallback**: Nếu SDK không có token, fallback về URL params
4. **Login**: Sử dụng token để đăng nhập
5. **Close App**: Khi cần đóng app, gọi `closeApp()`
## Test
### Local Development
```bash
# Test với mock SDK
./scripts/test_x_app_sdk.sh
```
### Production
Không cần thay đổi gì. x-app-sdk sẽ tự động detect Super App environment và hoạt động đúng.
**Lưu ý**: x-app-sdk được load từ npm package, tự động detect Super App environment.
## Troubleshooting
### SDK không khởi tạo được
- Kiểm tra console log: `❌ XAppSDKService: x-app-sdk not found in window`
- Đảm bảo Super App đã load x-app-sdk trước khi load mini app
### Token không lấy được
- Kiểm tra console log: `❌ SplashScreen - Failed to get token from SDK`
- Fallback sẽ tự động sử dụng URL params
- Kiểm tra Super App có expose `getToken()` method không
### CloseApp không hoạt động
- Kiểm tra console log: `❌ XAppSDKService: closeApp method not found`
- Fallback sẽ tự động sử dụng `window.history.back()` hoặc `window.close()`
## Files liên quan
- `lib/web/x_app_sdk_service.dart` - Service chính
- `lib/web/web_helper_web.dart` - Web implementation
- `lib/web/web_helper_stub.dart` - Stub cho non-web platforms
- `lib/web/web_helper.dart` - Export file
- `lib/screen/splash/splash_screen_viewmodel.dart` - Tích hợp trong splash
- `lib/base/app_navigator.dart` - Sử dụng closeApp
- `lib/core/app_initializer.dart` - Khởi tạo SDK
- `web/index.html` - Mock SDK cho development
- `scripts/test_x_app_sdk.sh` - Script test
{
"flavor":"dev",
"baseUrl":"https://api.sandbox.mypoint.com.vn/8854/gup2start/rest",
"t3Token":"runner-env-flavor-dev",
"enableLogging":true
"flavor":"pro",
"baseUrl":"https://api.mypoint.com.vn/8854/gup2start/rest",
"t3Token":"runner-env-flavor-pro",
"enableLogging":false
}
......@@ -2,6 +2,6 @@
"flavor":"dev",
"baseUrl":"https://api.sandbox.mypoint.com.vn/8854/gup2start/rest",
"t3Token":"runner-env-flavor-dev",
"enableLogging":true
"enableLogging":false
}
......@@ -39,16 +39,29 @@ copy_x_app_sdk() {
compress_assets() {
local dir="$1"
echo "🗜️ Compressing assets with maximum compression..."
# Gzip compression (level 9 = maximum)
if command -v gzip >/dev/null 2>&1; then
find "${dir}" -type f \( -name '*.js' -o -name '*.css' -o -name '*.json' -o -name '*.wasm' -o -name '*.svg' -o -name '*.html' \) -exec gzip -9 -kf {} \;
echo " → Creating .gz files (gzip -9)..."
find "${dir}" -type f \( -name '*.js' -o -name '*.css' -o -name '*.json' -o -name '*.wasm' -o -name '*.svg' -o -name '*.html' -o -name '*.woff2' -o -name '*.otf' -o -name '*.ttf' \) ! -name '*.gz' ! -name '*.br' -exec gzip -9 -kf {} \;
echo " ✅ Gzip compression completed"
else
echo "⚠️ gzip not available, skipping .gz artifacts"
echo " ⚠️ gzip not available, skipping .gz artifacts"
fi
# Brotli compression (quality 11 = maximum)
if command -v brotli >/dev/null 2>&1; then
find "${dir}" -type f \( -name '*.js' -o -name '*.css' -o -name '*.json' -o -name '*.wasm' -o -name '*.svg' -o -name '*.html' \) -exec brotli -f -k -q 11 {} \;
echo " → Creating .br files (brotli -q 11)..."
find "${dir}" -type f \( -name '*.js' -o -name '*.css' -o -name '*.json' -o -name '*.wasm' -o -name '*.svg' -o -name '*.html' -o -name '*.woff2' -o -name '*.otf' -o -name '*.ttf' \) ! -name '*.gz' ! -name '*.br' -exec brotli -f -k -q 11 {} \;
echo " ✅ Brotli compression completed"
else
echo "⚠️ brotli not available, skipping .br artifacts"
echo " ⚠️ brotli not available, skipping .br artifacts"
fi
# Calculate compression stats
local original_size=$(du -sk "${dir}" | cut -f1)
echo " 📊 Total size: ${original_size}KB (includes compressed files)"
}
echo "🚀 Building DEV export (optimized)..."
......@@ -56,17 +69,22 @@ set_env_dev
echo "🧹 Cleaning previous artifacts..."
flutter clean || true
rm -rf .dart_tool build || true
echo "📦 Getting Flutter packages..."
flutter pub get
echo "🔨 Flutter build web (release, CanvasKit)..."
echo "🔨 Flutter build web (release, MAXIMUM optimization for T3 delivery)..."
# --release: Build ở chế độ release (minified, optimized)
# --pwa-strategy=none: Tắt PWA service worker (giảm kích thước)
# --no-source-maps: Không tạo source maps (giảm kích thước build)
# --tree-shake-icons: Chỉ include icons được sử dụng (giảm kích thước fonts)
# --dart-define=FLUTTER_WEB_USE_SKIA=false: Force HTML renderer (không dùng CanvasKit)
flutter build web \
--release \
--dart-define=FLUTTER_WEB_USE_SKIA=true \
--dart-define=FLUTTER_WEB_USE_SKWASM=false \
--no-wasm-dry-run
--pwa-strategy=none \
--no-source-maps \
--tree-shake-icons \
--dart-define=FLUTTER_WEB_USE_SKIA=false
copy_x_app_sdk
......@@ -74,6 +92,69 @@ echo "📁 Preparing export directory: ${OUT_DIR}"
mkdir -p "${OUT_DIR}"
cp -r build/web/* "${OUT_DIR}/"
# Post-process index.html to fix duplicate loading issues
echo "🔧 Post-processing index.html to prevent duplicate script loading..."
if [ -f "${OUT_DIR}/index.html" ]; then
python3 - "${OUT_DIR}/index.html" <<'PYTHON_SCRIPT'
import re
import sys
file_path = sys.argv[1]
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Remove preload for main.dart.js if exists
content = re.sub(
r'<link\s+rel="preload"\s+href="main\.dart\.js"[^>]*>',
'<!-- Removed: main.dart.js preload (flutter_bootstrap.js will load it) -->',
content,
flags=re.IGNORECASE
)
# Ensure flutterConfiguration.renderer is set
if 'window.flutterConfiguration.renderer' not in content:
# Add renderer config before flutter_bootstrap.js loads
content = re.sub(
r'(window\.flutterConfiguration\s*=\s*window\.flutterConfiguration\s*\|\|\s*\{\};)',
r'\1\n window.flutterConfiguration.renderer = \'html\';',
content
)
# Remove duplicate script tags for flutter_bootstrap.js (keep only the last one)
script_tags = list(re.finditer(r'<script[^>]*src=["\']flutter_bootstrap\.js["\'][^>]*>', content, re.IGNORECASE))
if len(script_tags) > 1:
# Keep only the last one
for match in script_tags[:-1]:
content = content[:match.start()] + '<!-- Removed duplicate -->' + content[match.end():]
# Ensure flutter_bootstrap.js has async defer
content = re.sub(
r'(<script[^>]*src=["\']flutter_bootstrap\.js["\'][^>]*)(>)',
r'\1 async defer\2',
content,
flags=re.IGNORECASE
)
# Remove Branch SDK for web (only needed for mobile)
content = re.sub(
r'<link[^>]*cdn\.branch\.io[^>]*>',
'<!-- Removed: Branch SDK preconnect (not needed for web) -->',
content,
flags=re.IGNORECASE
)
content = re.sub(
r'<script[^>]*branch[^>]*>.*?</script>',
'<!-- Removed: Branch SDK (not needed for web) -->',
content,
flags=re.IGNORECASE | re.DOTALL
)
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
PYTHON_SCRIPT
echo "✅ Post-processed index.html"
fi
if [ -f "web/firebase-messaging-sw.js" ] && [ ! -f "${OUT_DIR}/firebase-messaging-sw.js" ]; then
cp web/firebase-messaging-sw.js "${OUT_DIR}/"
fi
......@@ -114,22 +195,47 @@ chmod +x "${OUT_DIR}/serve_local.sh"
if [ "${KEEP_CANVASKIT:-0}" != "1" ]; then
echo "🧹 Removing CanvasKit bundle to shrink export (set KEEP_CANVASKIT=1 to keep)..."
rm -rf "${OUT_DIR}/canvaskit"
rm -f "${OUT_DIR}/canvaskit.js" "${OUT_DIR}/canvaskit.wasm" 2>/dev/null || true
echo "✅ CanvasKit removed"
else
echo "ℹ️ KEEP_CANVASKIT=1 → giữ nguyên thư mục canvaskit."
fi
echo "🗜️ Precompressing assets..."
echo "🗜️ Precompressing assets with maximum compression..."
compress_assets "${OUT_DIR}"
echo "📦 Creating zip archive ${ZIP_FILE}..."
zip -rq "${ZIP_FILE}" "${OUT_DIR}"
# Calculate final sizes
echo ""
echo "📊 Build size summary:"
echo " Original files:"
du -sh "${OUT_DIR}" --exclude="*.gz" --exclude="*.br" 2>/dev/null || du -sh "${OUT_DIR}"
echo " With compression:"
du -sh "${OUT_DIR}"
echo ""
echo "🎉 DEV export ready!"
echo " Folder : ${OUT_DIR}"
echo " Zip : ${ZIP_FILE}"
echo "📦 Creating zip archive ${ZIP_FILE} (maximum compression)..."
zip -rq -9 "${ZIP_FILE}" "${OUT_DIR}"
echo ""
echo "🎉 DEV export ready (MAXIMUM OPTIMIZATION for T3 delivery)!"
echo " 📁 Folder : ${OUT_DIR}"
echo " 📦 Zip : ${ZIP_FILE}"
echo ""
echo "📊 Final sizes:"
zip_size=$(du -sh "${ZIP_FILE}" | cut -f1)
folder_size=$(du -sh "${OUT_DIR}" | cut -f1)
echo " 📦 Zip file: ${zip_size}"
echo " 📁 Folder: ${folder_size}"
echo ""
echo "📌 Delivery notes for T3:"
echo " ✅ All assets are pre-compressed (gzip + brotli)"
echo " ✅ CanvasKit removed (saves ~1.6MB)"
echo " ✅ Source maps removed (saves space)"
echo " ✅ PWA disabled (simpler deployment)"
echo " ✅ Tree-shaken icons (only used icons included)"
echo ""
echo "📌 Upload suggestion: serve files with Content-Encoding gzip/brotli where available."
echo " 💡 Server should serve with Content-Encoding: gzip or br"
echo " 💡 Check .gz and .br files are available for optimal performance"
echo ""
echo "▶️ Quick preview command:"
echo " cd ${OUT_DIR} && ./serve_local.sh 8080"
......
......@@ -37,16 +37,29 @@ copy_x_app_sdk() {
compress_assets() {
local dir="$1"
echo "🗜️ Compressing assets with maximum compression..."
# Gzip compression (level 9 = maximum)
if command -v gzip >/dev/null 2>&1; then
find "${dir}" -type f \( -name '*.js' -o -name '*.css' -o -name '*.json' -o -name '*.wasm' -o -name '*.svg' -o -name '*.html' \) -exec gzip -9 -kf {} \;
echo " → Creating .gz files (gzip -9)..."
find "${dir}" -type f \( -name '*.js' -o -name '*.css' -o -name '*.json' -o -name '*.wasm' -o -name '*.svg' -o -name '*.html' -o -name '*.woff2' -o -name '*.otf' -o -name '*.ttf' \) ! -name '*.gz' ! -name '*.br' -exec gzip -9 -kf {} \;
echo " ✅ Gzip compression completed"
else
echo "⚠️ gzip not available, skipping .gz artifacts"
echo " ⚠️ gzip not available, skipping .gz artifacts"
fi
# Brotli compression (quality 11 = maximum)
if command -v brotli >/dev/null 2>&1; then
find "${dir}" -type f \( -name '*.js' -o -name '*.css' -o -name '*.json' -o -name '*.wasm' -o -name '*.svg' -o -name '*.html' \) -exec brotli -f -k -q 11 {} \;
echo " → Creating .br files (brotli -q 11)..."
find "${dir}" -type f \( -name '*.js' -o -name '*.css' -o -name '*.json' -o -name '*.wasm' -o -name '*.svg' -o -name '*.html' -o -name '*.woff2' -o -name '*.otf' -o -name '*.ttf' \) ! -name '*.gz' ! -name '*.br' -exec brotli -f -k -q 11 {} \;
echo " ✅ Brotli compression completed"
else
echo "⚠️ brotli not available, skipping .br artifacts"
echo " ⚠️ brotli not available, skipping .br artifacts"
fi
# Calculate compression stats
local original_size=$(du -sk "${dir}" | cut -f1)
echo " 📊 Total size: ${original_size}KB (includes compressed files)"
}
echo "🚀 Building PRODUCTION export (optimized)..."
......@@ -54,19 +67,22 @@ write_prod_env
echo "🧹 Cleaning previous artifacts..."
flutter clean || true
rm -rf .dart_tool build || true
echo "📦 Getting Flutter packages..."
flutter pub get
echo "🔨 Flutter build web (release, CanvasKit, no source maps)..."
echo "🔨 Flutter build web (release, MAXIMUM optimization for T3 delivery)..."
# --release: Build ở chế độ release (minified, optimized)
# --pwa-strategy=none: Tắt PWA service worker (giảm kích thước)
# --no-source-maps: Không tạo source maps (giảm kích thước build)
# --tree-shake-icons: Chỉ include icons được sử dụng (giảm kích thước fonts)
# --dart-define=FLUTTER_WEB_USE_SKIA=false: Force HTML renderer (không dùng CanvasKit)
flutter build web \
--release \
--no-source-maps \
--pwa-strategy=none \
--dart-define=FLUTTER_WEB_USE_SKIA=true \
--dart-define=FLUTTER_WEB_USE_SKWASM=false \
--no-wasm-dry-run
--no-source-maps \
--tree-shake-icons \
--dart-define=FLUTTER_WEB_USE_SKIA=false
copy_x_app_sdk
......@@ -74,6 +90,69 @@ echo "📁 Preparing export directory: ${OUT_DIR}"
mkdir -p "${OUT_DIR}"
cp -r build/web/* "${OUT_DIR}/"
# Post-process index.html to fix duplicate loading issues and remove Branch SDK
echo "🔧 Post-processing index.html to prevent duplicate script loading and remove Branch SDK..."
if [ -f "${OUT_DIR}/index.html" ]; then
python3 - "${OUT_DIR}/index.html" <<'PYTHON_SCRIPT'
import re
import sys
file_path = sys.argv[1]
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Remove preload for main.dart.js if exists
content = re.sub(
r'<link\s+rel="preload"\s+href="main\.dart\.js"[^>]*>',
'<!-- Removed: main.dart.js preload (flutter_bootstrap.js will load it) -->',
content,
flags=re.IGNORECASE
)
# Ensure flutterConfiguration.renderer is set
if 'window.flutterConfiguration.renderer' not in content:
# Add renderer config before flutter_bootstrap.js loads
content = re.sub(
r'(window\.flutterConfiguration\s*=\s*window\.flutterConfiguration\s*\|\|\s*\{\};)',
r'\1\n window.flutterConfiguration.renderer = \'html\';',
content
)
# Remove duplicate script tags for flutter_bootstrap.js (keep only the last one)
script_tags = list(re.finditer(r'<script[^>]*src=["\']flutter_bootstrap\.js["\'][^>]*>', content, re.IGNORECASE))
if len(script_tags) > 1:
# Keep only the last one
for match in script_tags[:-1]:
content = content[:match.start()] + '<!-- Removed duplicate -->' + content[match.end():]
# Ensure flutter_bootstrap.js has async defer
content = re.sub(
r'(<script[^>]*src=["\']flutter_bootstrap\.js["\'][^>]*)(>)',
r'\1 async defer\2',
content,
flags=re.IGNORECASE
)
# Remove Branch SDK for web (only needed for mobile)
content = re.sub(
r'<link[^>]*cdn\.branch\.io[^>]*>',
'<!-- Removed: Branch SDK preconnect (not needed for web) -->',
content,
flags=re.IGNORECASE
)
content = re.sub(
r'<script[^>]*branch[^>]*>.*?</script>',
'<!-- Removed: Branch SDK (not needed for web) -->',
content,
flags=re.IGNORECASE | re.DOTALL
)
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
PYTHON_SCRIPT
echo "✅ Post-processed index.html"
fi
if [ -f "web/firebase-messaging-sw.js" ] && [ ! -f "${OUT_DIR}/firebase-messaging-sw.js" ]; then
cp web/firebase-messaging-sw.js "${OUT_DIR}/"
fi
......@@ -118,18 +197,41 @@ else
echo "ℹ️ KEEP_CANVASKIT=1 → giữ nguyên thư mục canvaskit."
fi
echo "🗜️ Precompressing assets..."
echo "🗜️ Precompressing assets with maximum compression..."
compress_assets "${OUT_DIR}"
echo "📦 Creating zip archive ${ZIP_FILE}..."
zip -rq "${ZIP_FILE}" "${OUT_DIR}"
# Calculate final sizes
echo ""
echo "📊 Build size summary:"
echo " Original files:"
du -sh "${OUT_DIR}" --exclude="*.gz" --exclude="*.br" 2>/dev/null || du -sh "${OUT_DIR}"
echo " With compression:"
du -sh "${OUT_DIR}"
echo ""
echo "📦 Creating zip archive ${ZIP_FILE} (maximum compression)..."
zip -rq -9 "${ZIP_FILE}" "${OUT_DIR}"
echo ""
echo "🎉 PRODUCTION export ready!"
echo " Folder : ${OUT_DIR}"
echo " Zip : ${ZIP_FILE}"
echo "🎉 PRODUCTION export ready (MAXIMUM OPTIMIZATION for T3 delivery)!"
echo " 📁 Folder : ${OUT_DIR}"
echo " 📦 Zip : ${ZIP_FILE}"
echo ""
echo "📊 Final sizes:"
zip_size=$(du -sh "${ZIP_FILE}" | cut -f1)
folder_size=$(du -sh "${OUT_DIR}" | cut -f1)
echo " 📦 Zip file: ${zip_size}"
echo " 📁 Folder: ${folder_size}"
echo ""
echo "📌 Delivery notes for T3:"
echo " ✅ All assets are pre-compressed (gzip + brotli)"
echo " ✅ CanvasKit removed (saves ~1.6MB)"
echo " ✅ Source maps removed (saves space)"
echo " ✅ PWA disabled (simpler deployment)"
echo " ✅ Tree-shaken icons (only used icons included)"
echo ""
echo "📌 Deliverable: send the zip (or folder) to the hosting team. Serve with gzip/brotli if possible."
echo " 💡 Server should serve with Content-Encoding: gzip or br"
echo " 💡 Check .gz and .br files are available for optimal performance"
echo ""
echo "▶️ Quick preview command:"
echo " cd ${OUT_DIR} && ./serve_local.sh 8080"
......
# Core App Architecture
## Overview
This directory contains the core initialization and configuration logic for the app, separated from the main.dart file for better organization and maintainability.
## Files
### `app_initializer.dart`
Main app initialization orchestrator that coordinates all app features:
- **Environment loading** (`loadEnv()`)
- **Data preferences** initialization
- **HTTP service** setup
- **GetX controllers** registration
- **Firebase** initialization (mobile only)
- **User point** fetching (if logged in)
- **Web-specific** features initialization
- **Post-initialization** callbacks setup
### `web_app_initializer.dart`
Web-specific initialization and configuration:
- **URL parameters** handling
- **X-App-SDK** integration
- **Super App** communication
- **Fallback methods** for data retrieval
- **Retry mechanisms** for data loading
## Usage
### Main App Initialization
```dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize all app features
await AppInitializer.initialize();
// Run the app
runApp(const MyApp());
// Setup post-initialization callbacks
AppInitializer.setupPostInitCallbacks();
}
```
### Web-Specific Initialization
```dart
// Automatically called by AppInitializer.initialize()
await WebAppInitializer.initialize();
```
## Benefits
1. **Separation of Concerns**: Web logic separated from core app logic
2. **Maintainability**: Easier to modify and test individual components
3. **Readability**: Main.dart is now clean and focused
4. **Reusability**: Initialization logic can be reused in tests
5. **Modularity**: Easy to add new platform-specific initializers
## Architecture
```
main.dart
├── AppInitializer.initialize()
│ ├── Environment loading
│ ├── Data preferences
│ ├── HTTP service
│ ├── GetX controllers
│ ├── Firebase (mobile only)
│ ├── User point fetching
│ └── WebAppInitializer.initialize()
│ ├── URL parameters handling
│ ├── X-App-SDK initialization
│ └── Super App communication
└── AppInitializer.setupPostInitCallbacks()
├── App loading
├── Notification handling
└── Other post-init tasks
```
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/dio_http_service.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import 'package:mypoint_flutter_app/preference/point/point_manager.dart';
import 'package:mypoint_flutter_app/screen/home/header_home_viewmodel.dart';
import 'package:mypoint_flutter_app/firebase/push_notification.dart';
import 'package:mypoint_flutter_app/firebase/push_setup.dart';
import 'package:mypoint_flutter_app/base/app_loading.dart';
import 'package:mypoint_flutter_app/env_loader.dart';
import 'package:mypoint_flutter_app/web/web_helper.dart';
import 'package:mypoint_flutter_app/core/deep_link_service.dart';
import '../firebase/push_setup.dart';
/// Main app initialization and setup
class AppInitializer {
......@@ -27,13 +24,13 @@ class AppInitializer {
// Initialize GetX controllers
Get.put(HeaderThemeController(), permanent: true);
// Initialize Firebase (mobile only)
await _initializeFirebase();
// await _initializeFirebase();
// Fetch user point if logged in
await _fetchUserPointIfLoggedIn();
// await _fetchUserPointIfLoggedIn();
// Initialize web-specific features (including x-app-sdk)
await _initializeWebFeatures();
// Initialize deep link handlers (Branch, URI schemes)
await DeepLinkService().initialize();
// await DeepLinkService().initialize();
debugPrint('✅ App initialization completed');
}
......@@ -102,25 +99,28 @@ class AppInitializer {
AppLoading().attach();
});
// Handle launch from notification when app was killed
_handleInitialNotificationLaunch();
// _handleInitialNotificationLaunch();
// Handle launch from local notification tap when app was killed
handleLocalNotificationLaunchIfAny();
// handleLocalNotificationLaunchIfAny();
} catch (e) {
debugPrint('Error in setupPostInitCallbacks: $e');
}
}
/// Handle initial notification launch
/// Handle initial notification launch (mobile only)
static Future<void> _handleInitialNotificationLaunch() async {
if (kIsWeb) return; // Skip for web
try {
final initial = await FirebaseMessaging.instance.getInitialMessage();
debugPrint('Checking initial message for app launch from terminated state...$initial');
if (initial == null) return;
WidgetsBinding.instance.addPostFrameCallback((_) {
Future.delayed(const Duration(seconds: 1), () {
NotificationRouter.handleRemoteMessage(initial);
});
});
// FirebaseMessaging is only available on mobile
// This code will not be included in web builds due to conditional imports
// final initial = await FirebaseMessaging.instance.getInitialMessage();
// debugPrint('Checking initial message for app launch from terminated state...$initial');
// if (initial == null) return;
// WidgetsBinding.instance.addPostFrameCallback((_) {
// Future.delayed(const Duration(seconds: 1), () {
// NotificationRouter.handleRemoteMessage(initial);
// });
// });
} catch (_) {}
}
}
......@@ -36,7 +36,7 @@ class _OnboardingScreenState extends BaseState<OnboardingScreen> with BasicState
@override
Widget createBody() {
final placeholderAsset = kIsWeb ? 'assets/images/splash_screen.png' : 'assets/images/bg_onboarding.png';
final placeholderAsset = kIsWeb ? 'assets/images/splash_screen.webp' : 'assets/images/bg_onboarding.png';
return GestureDetector(
onTap: hideKeyboard,
child: Scaffold(
......
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/screen/splash/splash_screen_viewmodel.dart';
......@@ -21,6 +22,7 @@ class _SplashScreenState extends BaseState<SplashScreen> with BasicState {
@override
void initState() {
super.initState();
print('🚀 SplashScreen - initState ${DateTime.now().toString()}');
_viewModel.checkUpdateResponse = (data) {
WidgetsBinding.instance.addPostFrameCallback((_) {
final updateData = (data?.updateRequest ?? []).firstOrNull;
......@@ -40,16 +42,16 @@ class _SplashScreenState extends BaseState<SplashScreen> with BasicState {
@override
Widget createBody() {
final path = kIsWeb ? "assets/images/splash_screen.webp" : "assets/images/splash_screen.png";
return Scaffold(
backgroundColor: Colors.blue,
body: Stack(
children: [
Container(
Image.asset(
path,
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
image: DecorationImage(image: AssetImage("assets/images/splash_screen.png"), fit: BoxFit.cover),
),
fit: BoxFit.cover,
),
Center(child: CircularProgressIndicator()),
],
......
......@@ -18,9 +18,13 @@ class SplashScreenViewModel extends RestfulApiViewModel {
void Function(UpdateResponseModel? data)? checkUpdateResponse;
var _updateLink = '';
static const Duration _networkTimeout = Duration(seconds: 20);
static const Duration _sdkTimeout = Duration(seconds: 20);
static const Duration _sdkTimeout = Duration(seconds: 10);
Future<void> checkUpdateApp() async {
if (kIsWeb) {
checkUpdateResponse?.call(null);
return;
}
try {
final response = await client.checkUpdateApp().timeout(_networkTimeout);
_updateLink = response.data?.updateRequest?.firstOrNull?.updateLink.orEmpty ?? '';
......
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/screen/news/news_list_screen.dart';
import 'package:mypoint_flutter_app/screen/qr_code/qr_code_screen.dart';
import '../deferred/deferred_routes.dart';
import '../screen/affiliate_brand_detail/affiliate_brand_detail_screen.dart';
import '../screen/affiliate_brand_detail/affiliate_brand_list_screen.dart';
import '../screen/affiliate_brand_detail/affiliate_category_grid_screen.dart';
import '../screen/contacts/contacts_list_screen.dart';
import '../screen/game/game_cards/game_card_screen.dart';
import '../screen/game/game_tab_screen.dart';
import '../screen/health_book/health_book_card_detail.dart';
import '../screen/interested_categories/interestied_categories_screen.dart';
import '../screen/login/login_screen.dart';
import '../screen/main_tab_screen/main_tab_screen.dart';
import '../screen/notification/notification_detail_screen.dart';
......@@ -20,14 +12,6 @@ import '../screen/personal/personal_edit_screen.dart';
import '../screen/register_campaign/register_form_input_screen.dart';
import '../screen/setting/setting_screen.dart';
import '../screen/splash/splash_screen.dart';
import '../screen/support/support_screen.dart';
import '../screen/topup/topup_screen.dart';
import '../screen/transaction/history/transaction_history_detail_screen.dart';
import '../screen/transaction/transaction_detail_screen.dart';
import '../screen/voucher/detail/voucher_detail_screen.dart';
import '../screen/voucher/mobile_card/my_mobile_card_detail_widget.dart';
import '../screen/voucher/mobile_card/my_mobile_card_list_widget.dart';
import '../screen/voucher/my_voucher/my_product_list_widget.dart';
import '../screen/voucher/voucher_list/voucher_list_screen.dart';
import '../screen/webview/web_view_screen.dart';
const splashScreen = '/splash';
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment