# 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)
- [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)

## 🚀 Getting Started

### Prerequisites

- 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)

### 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` và `export_dev.sh` → copy từ `env_dev.json`
- `run_prod.sh` và `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
```

### 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
```

### Kiểm tra cấu hình Nginx

```bash
nginx -t -c ~/nginx-gzip.conf
```

## 📱 X-App-SDK Integration

### Tổng quan

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.

### Cài đặt

```bash
npm install x-app-sdk@^1.1.5
```

**Lưu ý:** Version trong `package.json` là `^1.1.5`

### 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()`

### 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
```

## 📝 Scripts Reference

### Build Scripts

- `./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)

### Export Output

- Development: `web_dev_export_YYYYMMDD_HHMMSS/` và `.zip`
- Production: `web_prod_export_YYYYMMDD_HHMMSS/` và `.zip`

Mỗi thư mục export chứa:
- Tất cả files từ `build/web/`
- Pre-compressed `.gz` và `.br` files
- `serve_local.sh` script để test local (Python HTTP server)

## 📄 License

[Thêm thông tin license nếu có]

## 👥 Contributors

[Thêm danh sách contributors nếu có]
