Commit 0b973e61 authored by DatHV's avatar DatHV
Browse files

update build web, ui

parent b7cceccb
# Close App Integration với x-app-sdk
## 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
- `lib/web/x_app_sdk_service.dart` - Service gọi JavaScript
- `web/index.html` - JavaScript implementation
- `lib/web/close_app_example.dart` - Ví dụ sử dụng
# 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
```
### **Test x-app-sdk:**
```bash
./scripts/test_x_app_sdk.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_x_app_sdk.sh # Test x-app-sdk
└── lib/web/ # x-app-sdk integration
```
## ✅ **Kết quả:**
-**Đơn giản**: Chỉ 2 lệnh chính
-**Dễ nhớ**: `./run_dev.sh``./run_prod.sh`
-**x-app-sdk**: 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
#### **3. Tích hợp x-app-sdk:**
- `lib/web/x_app_sdk_service.dart` - Service chính
- `lib/web/web_helper_web.dart` - Web helper functions
- `lib/web/web_helper_stub.dart` - Stub cho non-web
- `web/index.html` - JavaScript integration
- `X_APP_SDK_INTEGRATION.md` - Hướng dẫn tích hợp
## 🚀 **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
## 🔧 **x-app-sdk Integration:**
### **Chức năng:**
- Lấy token từ Super App: `window.getToken()`
- Lấy user info từ Super App: `window.getInfo('USER_ID')`
- Retry mechanism: Tối đa 3 lần
- Fallback: URL parameters và localStorage
### **Test:**
```bash
# Test với URL parameters
http://localhost:8080?token=abc123&userId=user456
# Test x-app-sdk
./scripts/test_x_app_sdk.sh
```
## 📁 **Cấu trúc đơn giản:**
```
flutter_app_mypoint/
├── assets/config/
│ └── env.json # Config duy nhất
├── lib/web/
│ ├── x_app_sdk_service.dart # x-app-sdk service
│ ├── web_helper_web.dart # Web functions
│ └── web_helper_stub.dart # Stub functions
├── web/
│ └── index.html # JavaScript integration
├── export_web.sh # Export script
└── scripts/
├── run_web_complete.sh # Development
├── export_and_run.sh # Export + run
└── test_x_app_sdk.sh # Test x-app-sdk
```
## 🎯 **Kết quả:**
-**Đơn giản**: Chỉ 1 config file
-**Dễ hiểu**: Không còn phức tạp
-**x-app-sdk**: Tích hợp đầy đủ
-**Sẵn sàng**: Export và deploy ngay
# X-App-SDK Integration Guide for Mini App
## Tổng quan
Tài liệu này mô tả cách tích hợp mini app với `x-app-sdk` để lấy thông tin token, user từ Super App. Super App đã có sẵn `x-app-sdk`, mini app chỉ cần gọi các method để lấy dữ liệu.
## Cài đặt
### 1. Build Flutter web app
```bash
flutter build web
```
## Cách sử dụng
### Trong Super App
Super App đã có sẵn `x-app-sdk` và cung cấp các method global:
```javascript
// Super App đã có sẵn các method này:
// window.getToken() - Lấy token đăng nhập
// window.getInfo(key) - Lấy thông tin người dùng theo key
// Mini app sẽ tự động gọi:
// window.getToken() - Lấy token
// window.getInfo('USER_ID') - Lấy ID người dùng
// window.getInfo('USER_NAME') - Lấy tên người dùng
// window.getInfo('USER_EMAIL') - Lấy email người dùng
// window.getInfo('USER_PHONE') - Lấy số điện thoại
```
### Trong Mini App (Flutter)
Mini app sẽ tự động lấy dữ liệu từ Super App:
```dart
// Lấy token
String? token = webGetAppHostToken();
// Lấy user info
Map<String, dynamic>? user = webGetAppHostUser();
// Kiểm tra data có sẵn
bool isReady = webIsAppHostDataReady();
// Kiểm tra x-app-sdk có sẵn từ Super App
bool sdkAvailable = webIsSDKAvailable();
// Lấy error message nếu có
String? error = webGetAppHostError();
// Lấy thông tin user theo key cụ thể
dynamic userInfo = await webGetUserInfoByKey('USER_NAME');
// Lấy token bất đồng bộ
String? token = await webGetTokenAsync();
```
## API Reference
### Web Helper Functions
- `webGetAppHostToken()`: Lấy token từ Super App
- `webGetAppHostUser()`: Lấy thông tin user từ Super App
- `webIsAppHostDataReady()`: Kiểm tra data có sẵn
- `webIsSDKAvailable()`: Kiểm tra x-app-sdk có sẵn từ Super App
- `webGetAppHostError()`: Lấy error message
- `webInitializeXAppSDK()`: Khởi tạo x-app-sdk service
- `webStoreAppHostData(token, user)`: Lưu data vào localStorage
- `webClearAppHostData()`: Xóa data
- `webCallXAppSDKMethod(methodName, args)`: Gọi method của x-app-sdk
- `webGetUserInfoByKey(key)`: Lấy thông tin user theo key
- `webGetTokenAsync()`: Lấy token bất đồng bộ
### XAppSDKService
```dart
final service = XAppSDKService();
// Khởi tạo
await service.initialize();
// Lấy dữ liệu
String? token = service.token;
Map<String, dynamic>? user = service.user;
bool isReady = service.isReady;
String? error = service.error;
// Lưu dữ liệu
service.storeData(token, user);
// Xóa dữ liệu
service.clearData();
// Gọi method SDK trực tiếp
dynamic result = await service.callSDKMethod('getInfo', ['USER_NAME']);
// Lấy thông tin user theo key
dynamic userInfo = await service.getUserInfo('USER_EMAIL');
// Lấy token bất đồng bộ
String? token = await service.getTokenAsync();
// Kiểm tra SDK có sẵn
bool sdkAvailable = service.isSDKAvailable();
```
## X-App-SDK Methods (từ Super App)
### getToken()
Lấy token đăng nhập người dùng từ Super App.
```javascript
// Super App cung cấp method này
window.getToken().then(token => {
console.log('Token người dùng:', token);
}).catch(error => {
console.error('Lỗi lấy token:', error);
});
```
### getInfo(key)
Lấy thông tin người dùng từ Super App theo key.
```javascript
// Super App cung cấp method này
// Lấy user ID
window.getInfo('USER_ID').then(info => {
console.log('ID người dùng:', info.data);
}).catch(error => {
console.error('Lỗi lấy thông tin:', error);
});
// Lấy thông tin khác
window.getInfo('USER_NAME').then(info => {
console.log('Tên người dùng:', info.data);
});
```
### Available Keys
- `'USER_ID'`: ID người dùng
- `'USER_NAME'`: Tên người dùng
- `'USER_EMAIL'`: Email người dùng
- `'USER_PHONE'`: Số điện thoại
## Fallback Methods
Nếu `x-app-sdk` không khả dụng, app sẽ thử các phương pháp fallback:
1. **URL Parameters**: `?token=xxx&user=yyy`
2. **localStorage**: `app_host_token`, `app_host_user`
## Debugging
Để debug, mở Developer Tools và kiểm tra:
1. Console logs với prefix `🔍`, `✅`, `❌`, `⚠️`
2. `window.AppHostData` object
3. Network tab để xem việc load x-app-sdk
## Lưu ý
- Chỉ hoạt động trên web platform
- Super App đã có sẵn `x-app-sdk` library
- Mini app gọi `window.getToken()``window.getInfo()` từ Super App
- Data được lưu trong localStorage để sử dụng lại
- App tự động listen cho updates từ Super App
- SDK với dữ liệu thật chỉ được trả ra khi chạy trên Super App
- Khi chạy trên web thường sẽ có data mẫu có cấu trúc tương tự data thật
## Troubleshooting
### Lỗi thường gặp:
1. **"x-app-sdk not available from Super App"**: Super App chưa expose methods
2. **"No data available from Super App"**: Không lấy được data từ Super App
3. **CORS errors**: Cần cấu hình CORS cho Super App
### Giải pháp:
1. Kiểm tra Super App đã expose `window.getToken()``window.getInfo()` chưa
2. Kiểm tra console logs để xem chi tiết lỗi
3. Sử dụng fallback methods nếu cần
4. Kiểm tra `webIsSDKAvailable()` để xác nhận SDK có sẵn
{
"flavor":"pro",
"baseUrl":"https://api.mypoint.com.vn/8854/gup2start/rest",
"t3Token":"runner-env-t3Token-pro",
"enableLogging":false
"flavor":"dev",
"baseUrl":"https://api.sandbox.mypoint.com.vn/8854/gup2start/rest",
"t3Token":"runner-env-flavor-dev",
"enableLogging":true
}
{
"flavor":"dev",
"baseUrl":"https://api.sandbox.mypoint.com.vn/8854/gup2start/rest",
"t3Token":"runner-env-flavor-dev",
"enableLogging":true
}
{
"status" : "success",
"data" : [
{
"images" : [
{
"id" : "181",
"image_type" : "COVER",
"image_url" : "https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/6A4B1C7EBC63C07BC19EAB8FDC58A86F/1733913751"
}
],
"id" : "196",
"achievement_name" : "Chương trình tặng quà bạn mới tháng 12.2024",
"achievement_icon_url" : "https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/6A4B1C7EBC63C07BC19EAB8FDC58A86F/1735639708",
"apply_for_group" : "",
"click_action_param" : "18721",
"click_action_type" : "VIEW_WEBSITE_PAGE"
},
{
"images" : [
{
"id" : "179",
"image_type" : "COVER",
"image_url" : "https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/9528B426658451B707E09A08EF06714E/1733381413"
}
],
"id" : "194",
"achievement_name" : "Gói cước bundle nhận 2GB",
"achievement_icon_url" : "https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/9528B426658451B707E09A08EF06714E/1735195967",
"apply_for_group" : "",
"click_action_param" : "18732",
"click_action_type" : "VIEW_WEBSITE_PAGE"
},
{
"images" : [
{
"id" : "129",
"image_type" : "COVER",
"image_url" : "https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/DED0F855CB537AB47C5043937DFD6FBD/1730254240"
}
],
"id" : "141",
"achievement_name" : "Khảo sát khách hàng",
"achievement_icon_url" : "https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/DED0F855CB537AB47C5043937DFD6FBD/1730376862",
"apply_for_group" : "",
"click_action_param" : "https://tichdiem.mypoint.com.vn/khaosatungdung2024",
"click_action_type" : "VIEW_LINK"
}
],
"code" : 200
}
{
"status" : "success",
"data" : [
{
"ios_version" : null,
"android_version_condition" : null,
"event_color" : "#5800d4",
"phone_number" : "",
"click_action_param" : "APP_SCREEN_PRODUCT_MOBILE_TOPUP",
"service_name" : "Nạp tiền điện thoại",
"image_url" : "https://api.mypoint.com.vn/8854/gup2start/rest/campaign-service-v3/api/v3.0/imageReader/F6A38CA6BCE66097D00855296F7DF8FFD4A0362D953D21B61274EC293CB2EAC6359F1B8975D551D7B610D89C7AB2532A86B361A991FAFF07295065D61FDB57BD/3486955098",
"android_version" : null,
"click_action_type" : "VIEW_APP_SCREEN",
"event_describle" : "+1%",
"ios_version_condition" : null,
"service_icon" : "",
"service_code" : "Topup"
},
{
"ios_version" : null,
"android_version_condition" : null,
"event_color" : "#c62a2a",
"phone_number" : "",
"click_action_param" : "APP_SCREEN_SIM_SERVICE",
"service_name" : "Gói cước nhà mạng",
"image_url" : "https://api.mypoint.com.vn/8854/gup2start/rest/campaign-service-v3/api/v3.0/imageReader/F6A38CA6BCE66097D00855296F7DF8FFB2877AADA2577FE35AC65C48B861F046AFC362121B94BE73237BBE955FE84DEE/3485776902",
"android_version" : null,
"click_action_type" : "VIEW_APP_SCREEN",
"event_describle" : "HOT",
"ios_version_condition" : null,
"service_icon" : "",
"service_code" : "GCNM"
},
{
"ios_version" : null,
"android_version_condition" : null,
"event_color" : "#d13333",
"phone_number" : "",
"click_action_param" : "APP_SCREEN_POINTBACK",
"service_name" : "Mua sắm tích điểm",
"image_url" : "https://api.mypoint.com.vn/8854/gup2start/rest/campaign-service-v3/api/v3.0/imageReader/F6A38CA6BCE66097D00855296F7DF8FF0453D2FA21F047DCC7FFE1F331CD5E14BCFF10055A29A4116DDA7AE3242E8047/3485776940",
"android_version" : null,
"click_action_type" : "VIEW_APP_SCREEN",
"event_describle" : "30%",
"ios_version_condition" : null,
"service_icon" : "",
"service_code" : "HD"
},
{
"ios_version" : "1.21.0",
"android_version_condition" : ">",
"event_color" : "#fad900",
"phone_number" : "",
"click_action_param" : "APP_SCREEN_GAME_BUNDLE",
"service_name" : "Game có thưởng",
"image_url" : "https://api.mypoint.com.vn/8854/gup2start/rest/campaign-service-v3/api/v3.0/imageReader/F6A38CA6BCE66097D00855296F7DF8FF0A40EF554F9782FD0B01841B092E70305AB966ED36F39B311FA34CA3529BB5E3/3485776974",
"android_version" : "1.21.3",
"click_action_type" : "VIEW_APP_SCREEN",
"event_describle" : "Mới",
"ios_version_condition" : ">",
"service_icon" : "",
"service_code" : "GAMEBUNDLE"
},
{
"ios_version" : null,
"android_version_condition" : null,
"event_color" : null,
"phone_number" : "",
"click_action_param" : "APP_SCREEN_MOBILE_TOPUP_DATA",
"service_name" : "Điểm đổi Data",
"image_url" : "https://api.mypoint.com.vn/8854/gup2start/rest/campaign-service-v3/api/v3.0/imageReader/F6A38CA6BCE66097D00855296F7DF8FF790F21B0AD1C117C554F60943AB9C1782974A7F8CB494DD2364C8435579AFD31451AD20EEC43461B9FA6BA09E969A183/3485777004",
"android_version" : null,
"click_action_type" : "VIEW_APP_SCREEN",
"event_describle" : "",
"ios_version_condition" : null,
"service_icon" : "",
"service_code" : "TDT"
},
{
"ios_version" : null,
"android_version_condition" : null,
"event_color" : null,
"phone_number" : "",
"click_action_param" : "20613",
"service_name" : "Thẻ Sức Khỏe",
"image_url" : "https://api.mypoint.com.vn/8854/gup2start/rest/campaign-service-v3/api/v3.0/imageReader/F6A38CA6BCE66097D00855296F7DF8FF1C5862DD7A52AD835423B11AE44FFFBEBA20AF7A7E6AAA6714FE7904BA2DC27D6D46EA06E08D47756F833BE5010EF3C1/3486955618",
"android_version" : null,
"click_action_type" : "APP_SCREEN_CARD_FAMILY_MEDON",
"event_describle" : "",
"ios_version_condition" : null,
"service_icon" : "",
"service_code" : "MEDON"
},
{
"ios_version" : null,
"android_version_condition" : null,
"event_color" : "#1e0ab2",
"phone_number" : "",
"click_action_param" : "APP_SCREEN_CUSTOMER_DAILY_CHECKIN",
"service_name" : "Check-in tích điểm",
"image_url" : "https://api.mypoint.com.vn/8854/gup2start/rest/campaign-service-v3/api/v3.0/imageReader/F6A38CA6BCE66097D00855296F7DF8FFF23510242CAACD9DFB499AD3B178E2D1EEDD78330DD72BB1B988CC538BDAF02F/3486955490",
"android_version" : null,
"click_action_type" : "VIEW_APP_SCREEN",
"event_describle" : "+10Đ",
"ios_version_condition" : null,
"service_icon" : "",
"service_code" : "SD"
},
{
"ios_version" : null,
"android_version_condition" : null,
"event_color" : "#fad000",
"phone_number" : null,
"click_action_param" : "APP_SCREEN_NEWS",
"service_name" : "Tin tức MyPoint",
"image_url" : "https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/81056048804BFCE6FD28AA2078537E24/1704275512",
"android_version" : null,
"click_action_type" : "VIEW_APP_SCREEN",
"event_describle" : "Mới",
"ios_version_condition" : null,
"service_icon" : "",
"service_code" : ""
},
{
"ios_version" : "",
"android_version_condition" : null,
"event_color" : null,
"phone_number" : "",
"click_action_param" : "20765",
"service_name" : "Cứu hộ giao thông",
"image_url" : "https://api.mypoint.com.vn/8854/gup2start/rest/campaign-service-v3/api/v3.0/imageReader/F6A38CA6BCE66097D00855296F7DF8FF74AFF39D8838527272F3762B93A88CB860B810A47ABB945FEA09A209065317C0/3486955678",
"android_version" : "",
"click_action_type" : "APP_SCREEN_VNTRA_PACKAGE",
"event_describle" : "",
"ios_version_condition" : null,
"service_icon" : "",
"service_code" : "VNTRA"
},
{
"ios_version" : null,
"android_version_condition" : null,
"event_color" : null,
"phone_number" : "",
"click_action_param" : "18593",
"service_name" : "ezTicket",
"image_url" : "https://api.mypoint.com.vn/8854/gup2start/rest/campaign-service-v3/api/v3.0/imageReader/F6A38CA6BCE66097D00855296F7DF8FFB1203C75DA6116911D50CF123237DD2AFAEC88358EB52D8FD06807A94DD2201F/3486955718",
"android_version" : null,
"click_action_type" : "VIEW_WEBSITE_PAGE",
"event_describle" : "",
"ios_version_condition" : null,
"service_icon" : "",
"service_code" : "EZCLOUD"
}
],
"code" : 200
}
\ No newline at end of file
{
"data": {
"levels": [
{
"id": "10",
"membership_level_code": "DONG",
"membership_level_rank": "1",
"membership_level_name": "Hạng Đồng",
"membership_level_description": "Đồng",
"membership_level_content": "",
"membership_level_term_and_condition": "<p style=\"text-align: justify;\"><span style=\"font-size: 12pt;\"><strong>&lt;ic_membership_voucher&gt; Ưu đãi từ Mobifone<br /></strong></span></p><ul style=\"margin: 0px;\"> <li><span style=\"font-size: 12pt;\"> <span style=\"font-weight: 400;\">Tích điểm 1% khi nạp thẻ trên mọi hình thức (MyPoint, ngân hàng, MyMobiFone, thẻ cào,...)</span> </span></li> </ul>",
"logo": "https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/D21B72F4E31E03AB0C4A4D50436E2356/1673231765",
"upgrage_type": "PAG",
"refresh_level_after_months_from_start_date": "6",
"upgrade_when_counter_is_greater_or_equal": "100000.00",
"upgrade_when_counter_gmv_is_greater_or_equal": "1000000.00",
"upgrade_to_membership_level_id": "13",
"downgrade_level_when_counter_is_less_than": "0.00",
"downgrade_level_when_counter_gmv_is_less_than": "0.00",
"downgrade_to_membership_level_id": "0",
"accumulated_counter": {
"counter_point_value": "20",
"counter_gmv_value": "0"
},
"accumulated_gmv_counter_id": "85",
"level_text_color": "#872415",
"level_background_color": "#FFFFFF",
"images": [],
"membership_level_term_and_conditions": [
{
"icon": "ic_membership_voucher",
"title": "Ưu đãi từ Mobifone",
"content": "<ul style=\"margin: 0px;\"> <li><span style=\"font-size: 12pt;\"> <span style=\"font-weight: 400;\">Tích điểm 1% khi nạp thẻ trên mọi hình thức (MyPoint, ngân hàng, MyMobiFone, thẻ cào,...)</span> </span></li> </ul>"
}
],
"level_end_at_date": "30/06/2025",
"level_start_at_date": "2024-12-05"
},
{
"id": "13",
"membership_level_code": "HANG_BAC",
"membership_level_rank": "2",
"membership_level_name": "Hạng Bạc",
"membership_level_description": "Bạc",
"membership_level_content": "",
"membership_level_term_and_condition": "<p style=\"text-align: justify;\"><span style=\"font-size: 12pt;\"><strong>&lt;ic_membership_voucher&gt; Ưu đãi từ Mobifone<br /></strong></span></p><ul style=\"font-size: 12pt; font-weight: 400;\"><li>Tích điểm 1% khi nạp thẻ trên mọi hình thức (MyPoint, ngân hàng, MyMobiFone, thẻ cào,...)</li></ul><p>=====</p><p style=\"text-align: justify;\"><span style=\"font-size: 12pt;\"><strong>&lt;ic_membership_promotion&gt; Ưu đãi thăng hạng<br /></strong></span></p><ul style=\"font-size: 12pt; font-weight: 400;\"><li>Cộng 5.000 điểm vào tài khoản MyPoint</li></ul><p>=====</p><p style=\"text-align: justify;\"><span style=\"font-size: 12pt;\"><strong>&lt;ic_membership_cashback&gt; Ưu đãi hoàn điểm<br /></strong></span></p><ul style=\"font-size: 12pt; font-weight: 400;\"><li>Hoàn thêm 1.000 điểm cho các giao dịch trên 1 triệu</li></ul>",
"logo": "https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/02966D62A8E4A4443AAD3DF8C7CBB0C7/1672388594",
"upgrage_type": "PAG",
"refresh_level_after_months_from_start_date": "6",
"upgrade_when_counter_is_greater_or_equal": "600000.00",
"upgrade_when_counter_gmv_is_greater_or_equal": "3000000.00",
"upgrade_to_membership_level_id": "16",
"downgrade_level_when_counter_is_less_than": "100000.00",
"downgrade_level_when_counter_gmv_is_less_than": "1000000.00",
"downgrade_to_membership_level_id": "10",
"accumulated_gmv_counter_id": "85",
"level_text_color": "#353546",
"level_background_color": "#FFFFFF",
"images": [
{
"type": "BACKGROUND",
"caption": "",
"image_url": "https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/C65C9C99B2380470E4E7C3993C4C2D58/1672813121"
}
],
"membership_level_term_and_conditions": [
{
"icon": "ic_membership_voucher",
"title": "Ưu đãi từ Mobifone",
"content": "<ul style=\"font-size: 12pt; font-weight: 400;\"><li>Tích điểm 1% khi nạp thẻ trên mọi hình thức (MyPoint, ngân hàng, MyMobiFone, thẻ cào,...)</li></ul>"
},
{
"icon": "ic_membership_promotion",
"title": "Ưu đãi thăng hạng",
"content": "<ul style=\"font-size: 12pt; font-weight: 400;\"><li>Cộng 5.000 điểm vào tài khoản MyPoint</li></ul>"
},
{
"icon": "ic_membership_cashback",
"title": "Ưu đãi hoàn điểm",
"content": "<ul style=\"font-size: 12pt; font-weight: 400;\"><li>Hoàn thêm 1.000 điểm cho các giao dịch trên 1 triệu</li></ul>"
}
],
"level_end_at_date": "30/06/2025"
},
{
"id": "16",
"membership_level_code": "HANG_VANG",
"membership_level_rank": "3",
"membership_level_name": "Hạng Vàng",
"membership_level_description": "Vàng",
"membership_level_content": "",
"membership_level_term_and_condition": "<p style=\"text-align: justify;\"><span style=\"font-size: 12pt;\"><strong>&lt;ic_membership_voucher&gt; Ưu đãi từ Mobifone<br /></strong></span></p> <ul> <li><span style=\"font-size: 12pt;\"><span style=\"font-weight: 400;\">Tích điểm 1% khi nạp thẻ trên mọi hình thức (MyPoint, ngân hàng, MyMobiFone, thẻ cào,...)</span></span></li> </ul> <p>=====</p> <p style=\"text-align: justify;\"><span style=\"font-size: 12pt;\"><strong>&lt;ic_membership_promotion&gt; Ưu đãi thăng hạng<br /></strong></span></p> <ul> <li><span style=\"font-size: 12pt;\"><span style=\"font-weight: 400;\">Cộng 10.000 điểm vào tài khoản Mypoint</span></span></li> </ul> <p>=====</p> <p style=\"text-align: justify;\"><span style=\"font-size: 12pt;\"><strong>&lt;ic_membership_cashback&gt; Ưu đãi hoàn điểm<br /></strong></span></p> <ul> <li style=\"text-align: justify;\"><span style=\"font-size: 12pt;\"><span style=\"font-weight: 400;\">Hoàn thêm 2.000 điểm cho các giao dịch trên 1 triệu</span></span></li> </ul>",
"logo": "https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/914959AF1BCB1AAD2CC0C1D4ADD93954/1672389218",
"upgrage_type": "PAG",
"refresh_level_after_months_from_start_date": "6",
"upgrade_when_counter_is_greater_or_equal": "1000000.00",
"upgrade_when_counter_gmv_is_greater_or_equal": "5000000.00",
"upgrade_to_membership_level_id": "19",
"downgrade_level_when_counter_is_less_than": "1000000.00",
"downgrade_level_when_counter_gmv_is_less_than": "3000000.00",
"downgrade_to_membership_level_id": "13",
"accumulated_gmv_counter_id": "85",
"level_text_color": "#C97108",
"level_background_color": "#FFFFFF",
"images": [
{
"type": "BACKGROUND",
"caption": "",
"image_url": "https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/E08C495249C39294BB245319462843F1/1672396844"
}
],
"membership_level_term_and_conditions": [
{
"icon": "ic_membership_voucher",
"title": "Ưu đãi từ Mobifone",
"content": " <ul> <li><span style=\"font-size: 12pt;\"><span style=\"font-weight: 400;\">Tích điểm 1% khi nạp thẻ trên mọi hình thức (MyPoint, ngân hàng, MyMobiFone, thẻ cào,...)</span></span></li> </ul> "
},
{
"icon": "ic_membership_promotion",
"title": "Ưu đãi thăng hạng",
"content": " <ul> <li><span style=\"font-size: 12pt;\"><span style=\"font-weight: 400;\">Cộng 10.000 điểm vào tài khoản Mypoint</span></span></li> </ul> "
},
{
"icon": "ic_membership_cashback",
"title": "Ưu đãi hoàn điểm",
"content": " <ul> <li style=\"text-align: justify;\"><span style=\"font-size: 12pt;\"><span style=\"font-weight: 400;\">Hoàn thêm 2.000 điểm cho các giao dịch trên 1 triệu</span></span></li> </ul>"
}
],
"level_end_at_date": "30/06/2025"
},
{
"id": "19",
"membership_level_code": "KIM_CUONG",
"membership_level_rank": "4",
"membership_level_name": "Hạng Kim Cương",
"membership_level_description": "Kim Cương",
"membership_level_content": "",
"membership_level_term_and_condition": "<p style=\"text-align: justify;\"><span style=\"font-size: 12pt;\"><strong>&lt;ic_membership_voucher&gt; Ưu đãi từ Mobifone<br /></strong></span></p> <ul> <li><span style=\"font-size: 12pt;\"><span style=\"font-weight: 400;\">Tích điểm 1% khi nạp thẻ trên mọi hình thức (MyPoint, ngân hàng, MyMobiFone, thẻ cào,...)</span></span></li> </ul> <p>=====</p> <p style=\"text-align: justify;\"><span style=\"font-size: 12pt;\"><strong>&lt;ic_membership_promotion&gt; Ưu đãi thăng hạng<br /></strong></span></p> <ul> <li><span style=\"font-size: 12pt;\"><span style=\"font-weight: 400;\">Cộng 50.000 điểm vào tài khoản Mypoint</span></span></li> </ul> <p>=====</p> <p style=\"text-align: justify;\"><span style=\"font-size: 12pt;\"><strong>&lt;ic_membership_cashback&gt; Ưu đãi hoàn điểm<br /></strong></span></p> <ul> <li style=\"text-align: justify;\"><span style=\"font-size: 12pt;\"><span style=\"font-weight: 400;\">Hoàn thêm 5.000 điểm cho các giao dịch trên 1 triệu</span></span></li> </ul>",
"logo": "https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/1991F2B477B5C1D6239DD4B8720414D0/1672389305",
"upgrage_type": "PAG",
"refresh_level_after_months_from_start_date": "6",
"upgrade_when_counter_is_greater_or_equal": "2000000.00",
"upgrade_when_counter_gmv_is_greater_or_equal": "5000000.00",
"upgrade_to_membership_level_id": "0",
"downgrade_level_when_counter_is_less_than": "2000000.00",
"downgrade_level_when_counter_gmv_is_less_than": "5000000.00",
"downgrade_to_membership_level_id": "16",
"accumulated_gmv_counter_id": "85",
"level_text_color": "#0D0D0D",
"level_background_color": "#FFFFFFF",
"images": [
{
"type": "BACKGROUND",
"caption": "",
"image_url": "https://api.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/38806D88D7F201FB41AD2A39E18BD640/1672396855"
}
],
"membership_level_term_and_conditions": [
{
"icon": "ic_membership_voucher",
"title": "Ưu đãi từ Mobifone",
"content": " <ul> <li><span style=\"font-size: 12pt;\"><span style=\"font-weight: 400;\">Tích điểm 1% khi nạp thẻ trên mọi hình thức (MyPoint, ngân hàng, MyMobiFone, thẻ cào,...)</span></span></li> </ul> "
},
{
"icon": "ic_membership_promotion",
"title": "Ưu đãi thăng hạng",
"content": " <ul> <li><span style=\"font-size: 12pt;\"><span style=\"font-weight: 400;\">Cộng 50.000 điểm vào tài khoản Mypoint</span></span></li> </ul> "
},
{
"icon": "ic_membership_cashback",
"title": "Ưu đãi hoàn điểm",
"content": " <ul> <li style=\"text-align: justify;\"><span style=\"font-size: 12pt;\"><span style=\"font-weight: 400;\">Hoàn thêm 5.000 điểm cho các giao dịch trên 1 triệu</span></span></li> </ul>"
}
],
"level_end_at_date": "30/06/2025"
}
],
"membership_rule": "17629"
}
}
\ No newline at end of file
#!/bin/bash
# Script để export web app cho môi trường development
echo "🔧 Exporting Development Web App..."
# Kill server cũ
lsof -i :8080 | awk 'NR>1 {print $2}' | xargs kill -9 2>/dev/null || true
# Chuyển sang dev environment
./scripts/switch_env.sh dev
# Export web app
./export_web.sh
......@@ -15,14 +15,29 @@ flutter clean
rm -rf .dart_tool build
flutter pub get
# Install web dependencies for x-app-sdk
echo "📦 Installing web dependencies for x-app-sdk..."
cd web
if [ -f "package.json" ]; then
npm install
if [ $? -eq 0 ]; then
echo "✅ Web dependencies installed successfully!"
else
echo "⚠️ Warning: Failed to install web dependencies, continuing anyway..."
fi
else
echo "⚠️ No package.json found in web directory, skipping npm install"
fi
cd ..
# Tạo thư mục export
EXPORT_DIR="web_export_$(date +%Y%m%d_%H%M%S)"
echo "📁 Creating export directory: $EXPORT_DIR"
mkdir -p "$EXPORT_DIR"
# Build web app với tối ưu hóa (và tắt PWA/SW để tránh cache)
echo "🔨 Building Flutter web app (WASM, no PWA)..."
flutter build web --release --wasm --optimization-level=4 --source-maps --pwa-strategy=none
# Build web app (prefer HTML renderer). If your Flutter doesn't support --web-renderer, the dart-define will keep HTML path.
echo "🔨 Building Flutter web app (HTML renderer preferred)..."
flutter build web --release --source-maps --dart-define=FLUTTER_WEB_USE_SKIA=false
if [ $? -eq 0 ]; then
echo "✅ Build thành công!"
......@@ -111,10 +126,31 @@ Có thể override bằng query parameters: `?env=production`
Firebase config được load từ `firebase_options.dart`
Đảm bảo Firebase project được cấu hình đúng cho domain này.
## x-app-sdk Integration
App được tích hợp với x-app-sdk để giao tiếp với Super App:
- **Token retrieval**: Lấy token từ Super App
- **User info**: Lấy thông tin user từ Super App
- **Fallback methods**: URL parameters và localStorage
- **Retry mechanism**: Tối đa 3 lần retry
### Test x-app-sdk:
1. Mở `test_urls.html` để test với URL parameters
2. Kiểm tra console log để debug
3. Test với Super App environment
## CORS Configuration
App cần CORS headers để gọi API:
```
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, Accept, Origin
```
## Troubleshooting
- Nếu có lỗi CORS, cấu hình server để allow origin của domain
- Nếu có lỗi Firebase, kiểm tra domain trong Firebase console
- Nếu có lỗi assets, đảm bảo đường dẫn relative đúng
- Nếu có lỗi x-app-sdk, kiểm tra Super App environment
EOF
# Tạo file .htaccess cho Apache
......@@ -161,12 +197,13 @@ AddType application/json .json
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>
# CORS headers (uncomment if needed)
# <IfModule mod_headers.c>
# Header always set Access-Control-Allow-Origin "*"
# Header always set Access-Control-Allow-Methods "GET, POST, OPTIONS"
# Header always set Access-Control-Allow-Headers "Content-Type"
# </IfModule>
# CORS headers for API calls
<IfModule mod_headers.c>
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header always set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With, Accept, Origin"
Header always set Access-Control-Allow-Credentials "true"
</IfModule>
EOF
# Tạo file nginx.conf cho Nginx
......@@ -202,10 +239,11 @@ server {
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin-when-cross-origin";
# CORS headers (uncomment if needed)
# add_header Access-Control-Allow-Origin "*";
# add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
# add_header Access-Control-Allow-Headers "Content-Type";
# CORS headers for API calls
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With, Accept, Origin";
add_header Access-Control-Allow-Credentials "true";
}
EOF
......@@ -277,6 +315,13 @@ EOF
echo "3. Hoặc sử dụng Docker: docker-compose up -d"
echo "4. Hoặc sử dụng Node.js: npm install && npm start"
echo ""
echo "🔧 X-App-SDK Integration:"
echo "- Mini app đã được tích hợp x-app-sdk để lấy token và user info từ Super App"
echo "- Super App cần expose window.getToken() và window.getInfo() methods"
echo "- Retry mechanism: Tối đa 3 lần retry cho Super App data"
echo "- Fallback methods: URL parameters và localStorage"
echo "- Xem chi tiết trong X_APP_SDK_INTEGRATION.md"
echo ""
echo "🌐 Test local: cd $EXPORT_DIR && python3 -m http.server 8080"
echo "💡 Tip: Nếu muốn auto-run server, chạy: (cd $EXPORT_DIR && python3 -m http.server 8080 &)"
......
......@@ -24,4 +24,12 @@ class BaseResponseModel<T> {
return _$BaseResponseModelFromJson<T>(json, fromJsonT);
}
Map<String, dynamic> toJson(Object? Function(T value) toJsonT) => _$BaseResponseModelToJson<T>(this, toJsonT);
}
class EmptyCodable {
EmptyCodable.fromJson(dynamic json);
Map<String, dynamic> toJson() {
return {};
}
}
\ No newline at end of file
......@@ -46,7 +46,7 @@ class DeviceInfo {
static DeviceDetails? _cachedDetails;
static Future<String> getDeviceId() async {
_cachedDeviceId = "1c8a26fc-748f-4d1a-a064-af8d7874df6a";
_cachedDeviceId = "13sd26fc-748f-4d1a-a064-af8d7874d565";
// if (_cachedDeviceId != null) return _cachedDeviceId!;
// final prefs = await SharedPreferences.getInstance();
// String? deviceId = prefs.getString(_deviceIdPreferenceKey);
......
# 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_app_initializer.dart';
/// Main app initialization and setup
class AppInitializer {
/// Initialize all core app features
static Future<void> initialize() async {
print('🚀 Initializing app...');
// Load environment configuration
await loadEnv();
// Initialize data preferences
await DataPreference.instance.init();
// Initialize HTTP service
DioHttpService();
// Initialize GetX controllers
Get.put(HeaderThemeController(), permanent: true);
// Initialize Firebase (mobile only)
await _initializeFirebase();
// Fetch user point if logged in
await _fetchUserPointIfLoggedIn();
// Initialize web-specific features
await WebAppInitializer.initialize();
print('✅ App initialization completed');
}
/// Initialize Firebase and FCM (mobile only)
static Future<void> _initializeFirebase() async {
if (!kIsWeb) {
print('📱 Initializing Firebase for mobile...');
await initFirebaseAndFcm();
} else {
print('🌐 Skipping Firebase initialization for web');
}
}
/// Fetch user point if already logged in
static Future<void> _fetchUserPointIfLoggedIn() async {
if (DataPreference.instance.logged) {
print('💰 Fetching user point...');
await UserPointManager().fetchUserPoint();
} else {
print('👤 User not logged in, skipping point fetch');
}
}
/// Setup post-initialization callbacks
static void setupPostInitCallbacks() {
WidgetsBinding.instance.addPostFrameCallback((_) {
AppLoading().attach();
});
// Handle launch from notification when app was killed
_handleInitialNotificationLaunch();
// Handle launch from local notification tap when app was killed
handleLocalNotificationLaunchIfAny();
}
/// Handle initial notification launch
static Future<void> _handleInitialNotificationLaunch() async {
try {
final initial = await FirebaseMessaging.instance.getInitialMessage();
print('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 (_) {}
}
}
......@@ -42,7 +42,7 @@ class AppConfig {
Future<void> loadEnv() async {
Map<String, dynamic>? cfg;
if (kIsWeb) {
// Web platform: chỉ load từ assets
// Web platform: single source of truth. Scripts prepare env.json.
cfg = await _tryLoadAsset('assets/config/env.json');
} else if (Platform.isIOS) {
cfg = await _tryLoadAsset('assets/config/env.json');
......@@ -58,7 +58,7 @@ Future<void> loadEnv() async {
}
AppConfig.current = AppConfig.fromMap(cfg);
if (kDebugMode) {
print('✅ AppConfig loaded: flavor=${AppConfig.current.flavor}');
print('✅ AppConfig loaded: flavor=${AppConfig.current.flavor}, baseUrl=${AppConfig.current.baseUrl}');
}
}
......
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/app_navigator.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/resources/base_color.dart';
import 'package:mypoint_flutter_app/screen/home/header_home_viewmodel.dart';
import 'package:mypoint_flutter_app/shared/router_gage.dart';
import 'base/app_loading.dart';
import 'env_loader.dart';
import 'networking/dio_http_service.dart';
import 'firebase/push_notification.dart';
import 'firebase/push_setup.dart';
import 'configs/url_params.dart';
import 'web/web_helper.dart';
import 'package:mypoint_flutter_app/core/app_initializer.dart';
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await loadEnv();
await DataPreference.instance.init();
DioHttpService();
Get.put(HeaderThemeController(), permanent: true);
// Web không dùng FCM: bỏ init Firebase Messaging để tránh SW/permission issues
if (!kIsWeb) {
await initFirebaseAndFcm();
}
// Chỉ fetch điểm khi đã đăng nhập, tránh 403 khi web chưa có token
if (DataPreference.instance.logged) {
await UserPointManager().fetchUserPoint();
}
// Đọc URL parameters cho web
_handleWebUrlParams();
// Initialize all app features
await AppInitializer.initialize();
// Run the app
runApp(const MyApp());
WidgetsBinding.instance.addPostFrameCallback((_) {
AppLoading().attach();
});
// Handle launch from notification when app was killed
_handleInitialNotificationLaunch();
// Handle launch from local notification tap when app was killed
handleLocalNotificationLaunchIfAny();
// Setup post-initialization callbacks
AppInitializer.setupPostInitCallbacks();
}
void _handleWebUrlParams() {
print('🔍 _handleWebUrlParams called, kIsWeb: $kIsWeb');
if (!kIsWeb) return;
final uri = Uri.base;
print('🔍 Current URI: ${uri.toString()}');
final token = uri.queryParameters['token'];
final userId = uri.queryParameters['userId'];
print('🔍 Web URL Params: {token: $token, user_id: $userId}');
if (token != null && token.isNotEmpty) {
UrlParams.setToken(token);
UrlParams.setUserId(userId);
print('✅ Token set from URL: $token');
print('🔍 UrlParams after set: ${UrlParams.allParams}');
// Clean URL to remove query params
webReplaceUrl('/');
} else {
print('❌ No token found in URL parameters');
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
......@@ -92,16 +47,3 @@ class MyApp extends StatelessWidget {
);
}
}
Future<void> _handleInitialNotificationLaunch() async {
try {
final initial = await FirebaseMessaging.instance.getInitialMessage();
print('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 (_) {}
}
\ No newline at end of file
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_typedefs/rx_typedefs.dart';
import 'package:mypoint_flutter_app/web/web_helper.dart';
import '../configs/constants.dart';
import '../preference/data_preference.dart';
import '../resources/base_color.dart';
......@@ -18,16 +20,25 @@ class AppNavigator {
static BuildContext? get _ctx => key.currentContext;
static Future<void> showAuthAlertAndGoLogin(String message) async {
final description = 'Phiên đăng nhập của bạn đã hết hạn. Vui lòng đăng nhập lại để tiếp tục sử dụng ứng dụng.';
if (_authDialogShown || _ctx == null) return;
_authDialogShown = true;
final dataAlert = DataAlertModel(
title: "Thông Báo",
description: message.isNotEmpty ? message : 'Phiên đăng nhập đã hết hạn. Vui lòng đăng nhập lại.',
description: description, // message.isNotEmpty ? message : ,
localHeaderImage: "assets/images/ic_pipi_03.png",
buttons: [
AlertButton(
text: "Đã hiểu",
onPressed: () {
if (kIsWeb) {
webCloseApp({
'message': message.isNotEmpty ? message : description,
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
_authDialogShown = false;
return;
}
final phone = DataPreference.instance.phoneNumberUsedForLoginScreen;
if (phone.isNotEmpty) {
Get.offAllNamed(loginScreen, arguments: {'phone': phone});
......
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import 'package:uuid/uuid.dart';
......@@ -10,12 +11,20 @@ class RequestInterceptor extends Interceptor {
if (token!= null) {
options.headers[authKey] = "Bearer $token";
}
options.headers.addAll({
// Base headers for all platforms
Map<String, dynamic> headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Accept-Language': 'vi',
'X-Request-Id': Uuid().v4(),
});
};
// Only add X-Request-Id for non-web platforms to avoid CORS issues
if (!kIsWeb) {
headers['X-Request-Id'] = Uuid().v4();
}
options.headers.addAll(headers);
handler.next(options);
}
}
\ No newline at end of file
......@@ -97,7 +97,7 @@ extension RestfulAPIClientAllRequest on RestfulAPIClient {
}
Future<BaseResponseModel<CheckPhoneResponseModel>> checkPhoneNumber(String phone) async {
var deviceKey = "1c8a26fc-748f-4d1a-a064-af8d7874df6a"; //await DeviceInfo.getDeviceId();
var deviceKey = await DeviceInfo.getDeviceId();
var key = "$phone+_=$deviceKey/*8854";
final body = {"device_key": deviceKey, "phone_number": phone, "key": key.toSha256()};
print('body: $body');
......
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