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
# mypoint_flutter_app
# MyPoint Flutter App
Flutter application for MyPoint platform with web support, X-App-SDK integration, and optimized build system.
## 📋 Mục lục
## Getting started
- [Getting Started](#getting-started)
- [Quick Start](#quick-start)
- [Development](#development)
- [Production Build](#production-build)
- [Nginx Setup với Gzip](#nginx-setup-với-gzip)
- [X-App-SDK Integration](#x-app-sdk-integration)
- [Close App Integration](#close-app-integration)
- [Architecture](#architecture)
- [Troubleshooting](#troubleshooting)
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
## 🚀 Getting Started
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
### Prerequisites
## Add your files
- Flutter SDK ^3.7.0
- Node.js và npm (cho x-app-sdk)
- Nginx (cho production deployment với gzip) - macOS: `brew install nginx`
- Python 3 (cho local development server)
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
### Installation
```bash
# Clone repository
git clone <repository-url>
cd flutter_app_mypoint
# Install Flutter dependencies
flutter pub get
# Install Node.js dependencies (cho x-app-sdk)
npm install
```
## ⚡ Quick Start
### Development (chạy local với CORS disabled)
```bash
./run_dev.sh
```
Script này sẽ:
- Set environment sang Development
- Build Flutter web app với CanvasKit renderer
- Copy x-app-sdk bundle
- Start Python HTTP server với CORS headers trên port 8080
- Mở Chrome với CORS disabled
### Export Development (để test trên server)
```bash
./export_dev.sh
```
Tạo thư mục export: `web_dev_export_YYYYMMDD_HHMMSS/` với:
- Build optimized (HTML renderer, no CanvasKit, no source maps)
- Pre-compressed assets (gzip + brotli)
- Post-processed index.html
- File zip để deploy
### Export Production
```bash
./export_prod.sh
```
Tạo thư mục export: `web_prod_export_YYYYMMDD_HHMMSS/` với production config.
### Production Preview (local)
```bash
./run_prod.sh
```
Chạy production build trên localhost:8080 (không có CORS bypass).
## 🔧 Development
### Cấu trúc môi trường
```
assets/config/
├── env_dev.json # Config Development
└── env.json # Config đang active (được copy từ env_dev.json hoặc tạo bởi export_prod.sh)
```
**Lưu ý:** `env.json` được tự động cập nhật bởi các script:
- `run_dev.sh``export_dev.sh` → copy từ `env_dev.json`
- `run_prod.sh``export_prod.sh` → tạo production config trực tiếp
### Build Options
**Development (`run_dev.sh`):**
- CanvasKit renderer (`FLUTTER_WEB_USE_SKIA=true`)
- CORS headers enabled
- Chrome với CORS disabled
**Export Development (`export_dev.sh`):**
- HTML renderer (`FLUTTER_WEB_USE_SKIA=false`) - tối ưu kích thước
- No source maps
- No PWA
- Tree-shaken icons
- CanvasKit removed
- Pre-compressed assets (gzip + brotli)
**Export Production (`export_prod.sh`):**
- Tương tự export_dev.sh nhưng với production config
## 📦 Production Build
### Export Development
```bash
./export_dev.sh
```
**Kết quả:**
- Thư mục: `web_dev_export_YYYYMMDD_HHMMSS/`
- File zip: `web_dev_export_YYYYMMDD_HHMMSS.zip`
- Tất cả assets được pre-compress (gzip + brotli)
- CanvasKit removed (tiết kiệm ~1.6MB)
- Source maps removed
- PWA disabled
- Tree-shaken icons
### Export Production
```bash
./export_prod.sh
```
**Kết quả:**
- Thư mục: `web_prod_export_YYYYMMDD_HHMMSS/`
- File zip: `web_prod_export_YYYYMMDD_HHMMSS.zip`
- Production config: `baseUrl: "https://api.mypoint.com.vn/..."`
- Tối ưu tương tự export_dev.sh
## 🌐 Nginx Setup với Gzip
### Cài đặt Nginx (macOS)
```bash
brew install nginx
```
### Quy trình đầy đủ với Nginx
#### Cách 1: Tự động (Khuyến nghị)
```bash
./run_dev_nginx.sh
```
Script này sẽ tự động:
1. Build và export app (`./export_dev.sh`)
2. Tự động tìm thư mục export mới nhất
3. Tạo/cập nhật cấu hình Nginx tại `~/nginx-gzip.conf`
4. Start/Reload Nginx với gzip enabled
**Cấu hình Nginx được tạo:**
- Gzip static enabled (serve pre-compressed `.gz` files)
- Gzip compression cho: JS, CSS, JSON, WASM, fonts, SVG
- Cache headers cho static assets
- SPA routing support (fallback to `index.html`)
- Port: 8080
#### Cách 2: Thủ công
```bash
# 1. Build và export
./export_dev.sh
# 2. Start Nginx với config có sẵn
nginx -c ~/nginx-gzip.conf
# Hoặc reload nếu đã chạy
nginx -s reload -c ~/nginx-gzip.conf
```
cd existing_repo
git remote add origin https://git.i-com.vn/dathv/mypoint_flutter_app.git
git branch -M master
git push -uf origin master
### Kiểm tra Gzip hoạt động
1. Mở DevTools (F12) → Network tab
2. Reload trang
3. Click vào file `main.dart.js`
4. Kiểm tra Response Headers:
- Phải có `Content-Encoding: gzip`
- Size phải nhỏ hơn (khoảng 1.1M thay vì 3.8M)
### Dừng Nginx
```bash
nginx -s stop -c ~/nginx-gzip.conf
```
## Integrate with your tools
### Kiểm tra cấu hình Nginx
- [ ] [Set up project integrations](https://git.i-com.vn/dathv/mypoint_flutter_app/-/settings/integrations)
```bash
nginx -t -c ~/nginx-gzip.conf
```
## Collaborate with your team
## 📱 X-App-SDK Integration
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
### Tổng quan
## Test and Deploy
Mini app tích hợp với Super App thông qua `x-app-sdk` để:
- Lấy token từ Super App
- Đóng app và trả về Super App
- Cấu hình UI trong Super App
- Gọi điện, SMS, location, media picker, payment, v.v.
Use the built-in continuous integration in GitLab.
### Cài đặt
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
```bash
npm install x-app-sdk@^1.1.5
```
***
**Lưu ý:** Version trong `package.json``^1.1.5`
# Editing this README
### Cách sử dụng
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
#### Trong Super App
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
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.
## Name
Choose a self-explaining name for your project.
**Lưu ý**: Mini app sử dụng x-app-sdk thật từ npm package, không phải mock.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
#### Trong Mini App (Flutter)
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
Mini app sẽ tự động lấy token từ Super App khi khởi động:
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
```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()`
### 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
## 🚪 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
## 🏗️ Architecture
### Core App Initialization
This directory contains the core initialization and configuration logic for the app, separated from the main.dart file for better organization and maintainability.
#### App Initialization Flow
App được khởi tạo thông qua `AppInitializer`:
```
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
```
#### 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
**`deep_link_service.dart`**
Handles deep linking and navigation.
#### 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
### Cấu trúc thư mục
Xem chi tiết cấu trúc thư mục tại [PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md)
## 🔍 Troubleshooting
### CORS Error
**Lỗi:**
```
Access to XMLHttpRequest at 'https://api.sandbox.mypoint.com.vn/...'
from origin 'http://localhost:8080' has been blocked by CORS policy
```
**Giải pháp:**
- Sử dụng `./run_dev.sh` - tự động mở Chrome với CORS disabled
- Hoặc cài "CORS Unblock" browser extension
**Lưu ý:** Đây là vấn đề server-side, không phải client-side.
### Nginx không serve file .gz
**Kiểm tra:**
1. File `.gz` có tồn tại trong thư mục export không?
```bash
ls -lh web_dev_export_*/main.dart.js*
```
2. Nginx có module `gzip_static` không?
```bash
nginx -V 2>&1 | grep gzip_static
```
3. Client có gửi header `Accept-Encoding: gzip` không? (Trình duyệt tự động gửi)
**Giải pháp:**
- Đảm bảo đã chạy `./export_dev.sh` để tạo file `.gz`
- Sử dụng `./run_dev_nginx.sh` để tự động setup
- Reload Nginx: `nginx -s reload -c ~/nginx-gzip.conf`
### 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
- Kiểm tra `node_modules/x-app-sdk/dist/index.es.js` có tồn tại
### 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()`
### Build errors
**Flutter packages:**
```bash
flutter clean
flutter pub get
```
**Node packages:**
```bash
rm -rf node_modules package-lock.json
npm install
```
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## 📝 Scripts Reference
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
### Build Scripts
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
- `./run_dev.sh` - Chạy development với CORS disabled Chrome
- `./run_prod.sh` - Chạy production preview (local)
- `./export_dev.sh` - Build và export development với optimizations
- `./export_prod.sh` - Build và export production
- `./run_dev_nginx.sh` - Build + update Nginx + start Nginx (all-in-one)
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
### Export Output
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
- Development: `web_dev_export_YYYYMMDD_HHMMSS/``.zip`
- Production: `web_prod_export_YYYYMMDD_HHMMSS/``.zip`
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
Mỗi thư mục export chứa:
- Tất cả files từ `build/web/`
- Pre-compressed `.gz``.br` files
- `serve_local.sh` script để test local (Python HTTP server)
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## 📄 License
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
[Thêm thông tin license nếu có]
## License
For open source projects, say how it is licensed.
## 👥 Contributors
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
[Thêm danh sách contributors nếu có]
# 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 "📦 Creating zip archive ${ZIP_FILE} (maximum compression)..."
zip -rq -9 "${ZIP_FILE}" "${OUT_DIR}"
echo ""
echo "🎉 DEV export ready!"
echo " Folder : ${OUT_DIR}"
echo " Zip : ${ZIP_FILE}"
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 "🎉 PRODUCTION 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 "🎉 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