Commit a6797435 authored by DatHV's avatar DatHV
Browse files

refactor print, log, request

parent f0334970
#!/bin/bash
# Script để tự động thêm URL parameter handling vào index.html sau khi build
echo "🔧 Adding URL parameter handling to index.html..."
# Tìm file index.html trong build/web
INDEX_FILE="build/web/index.html"
if [ ! -f "$INDEX_FILE" ]; then
echo "❌ File $INDEX_FILE not found!"
exit 1
fi
# Tạo backup
cp "$INDEX_FILE" "$INDEX_FILE.backup"
# Không cần thêm JavaScript nữa vì Flutter đọc trực tiếp từ URL
echo "✅ No JavaScript needed - Flutter reads directly from URL query parameters"
echo "✅ Added URL parameter handling to $INDEX_FILE"
echo "🎉 Done! Now you can test with URLs like:"
echo " http://localhost:8080/?token=abc123"
echo " http://localhost:8080/?token=abc123&userId=user456"
#!/bin/bash
#!/usr/bin/env bash
# Script để export web app cho môi trường development và chạy với CORS
# Build and package the Flutter web app for the DEV environment with aggressive optimizations.
# Produces a timestamped folder + zip in the project root, with precompressed assets.
echo "🔧 Exporting Development Web App..."
set -euo pipefail
# Kill server cũ
lsof -i :8080 | awk 'NR>1 {print $2}' | xargs kill -9 2>/dev/null || true
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
OUT_DIR="web_dev_export_${TIMESTAMP}"
ZIP_FILE="${OUT_DIR}.zip"
# Export web app cho môi trường dev
./export_web.sh dev
cd "${PROJECT_ROOT}"
# Chạy server với CORS như run_dev
echo "🚀 Starting exported web app with CORS..."
EXPORT_DIRS=$(ls -d web_export_* 2>/dev/null | grep -v "\.zip$" | sort -r | head -1)
if [ -z "$EXPORT_DIRS" ]; then
echo "❌ No web export directory found"
set_env_dev() {
echo "🔧 Switching to DEVELOPMENT environment..."
local src="assets/config/env_dev.json"
if [ ! -f "${src}" ]; then
echo "❌ ${src} not found" >&2
exit 1
fi
echo "📁 Using export directory: $EXPORT_DIRS"
cd "$EXPORT_DIRS"
# Verify we're in the right directory
if [ ! -f "index.html" ]; then
echo "❌ index.html not found in $EXPORT_DIRS"
fi
cp "${src}" assets/config/env.json
echo "📋 Current config:"
cat assets/config/env.json
}
copy_x_app_sdk() {
local src="node_modules/x-app-sdk/dist/index.es.js"
local dest="build/web/js/x_app_sdk_bundle.js"
if [ ! -f "${src}" ]; then
echo "❌ x-app-sdk bundle not found at ${src}" >&2
exit 1
fi
mkdir -p "$(dirname "${dest}")"
cp "${src}" "${dest}"
echo "✅ Copied x-app-sdk bundle."
}
compress_assets() {
local dir="$1"
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 {} \;
else
echo "⚠️ gzip not available, skipping .gz artifacts"
fi
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 {} \;
else
echo "⚠️ brotli not available, skipping .br artifacts"
fi
}
echo "🚀 Building DEV export (optimized)..."
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, no source maps)..."
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
copy_x_app_sdk
echo "📁 Preparing export directory: ${OUT_DIR}"
mkdir -p "${OUT_DIR}"
cp -r build/web/* "${OUT_DIR}/"
if [ -f "web/firebase-messaging-sw.js" ] && [ ! -f "${OUT_DIR}/firebase-messaging-sw.js" ]; then
cp web/firebase-messaging-sw.js "${OUT_DIR}/"
fi
echo "✅ Found index.html in export directory"
# Helper script for local preview with SPA fallback
cat > "${OUT_DIR}/serve_local.sh" <<'EOF'
#!/usr/bin/env bash
# Start web server with CORS headers (same as run_web_complete.sh)
python3 -c "
PORT="${1:-8080}"
python3 - "$PORT" <<'PY'
import http.server
import socketserver
import socket
import os
import sys
ROOT = os.getcwd()
PORT = int(sys.argv[1])
class SPAHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
resolved = self.translate_path(self.path)
if not os.path.exists(resolved):
self.path = '/index.html'
return super().do_GET()
class CORSHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept, Origin')
super().end_headers()
def do_OPTIONS(self):
self.send_response(200)
self.end_headers()
def log_message(self, format, *args):
print(f'🌐 {format % args}')
def find_free_port(start_port=8080, max_attempts=10):
for port in range(start_port, start_port + max_attempts):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('', port))
return port
except OSError:
continue
return None
PORT = find_free_port(8080, 20)
if not PORT:
print('❌ No free port found')
exit(1)
print(f'🚀 Server running at http://localhost:{PORT}')
print(f'📁 Serving from: {os.getcwd()}')
print('🔧 CORS headers enabled for API calls')
print('')
print('Press Ctrl+C to stop the server')
with socketserver.TCPServer(('', PORT), CORSHTTPRequestHandler) as httpd:
def log_message(self, fmt, *args):
print(f"🌐 {fmt % args}")
with socketserver.TCPServer(('', PORT), SPAHandler) as httpd:
print(f"🚀 Serving {ROOT} at http://localhost:{PORT}")
print("💡 SPA fallback enabled. Ctrl+C to stop.")
httpd.serve_forever()
" &
SERVER_PID=$!
# Wait for server to start
sleep 3
# Open browser with CORS disabled (same as run_web_complete.sh)
echo "🌐 Opening browser with CORS disabled..."
if command -v open &> /dev/null; then
# macOS
open -n -a "Google Chrome" --args --disable-web-security --user-data-dir=/tmp/chrome_dev --disable-features=VizDisplayCompositor http://localhost:8080
elif command -v google-chrome &> /dev/null; then
# Linux
google-chrome --disable-web-security --user-data-dir=/tmp/chrome_dev --disable-features=VizDisplayCompositor http://localhost:8080 &
PY
EOF
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"
else
echo " Chrome not found. Please open manually: http://localhost:8080"
echo "KEEP_CANVASKIT=1 → giữ nguyên thư mục canvaskit."
fi
echo "🗜️ Precompressing assets..."
compress_assets "${OUT_DIR}"
echo "📦 Creating zip archive ${ZIP_FILE}..."
zip -rq "${ZIP_FILE}" "${OUT_DIR}"
echo ""
echo "✅ Setup complete!"
echo "🌐 Web app: http://localhost:8080"
echo "🔧 CORS disabled in browser for development"
echo "📁 Export directory: $EXPORT_DIRS"
echo "🎉 DEV export ready!"
echo " Folder : ${OUT_DIR}"
echo " Zip : ${ZIP_FILE}"
echo ""
echo "Press Ctrl+C to stop the server"
# Wait for user to stop
wait $SERVER_PID
echo "📌 Upload suggestion: serve files with Content-Encoding gzip/brotli where available."
echo ""
echo "▶️ Quick preview command:"
echo " cd ${OUT_DIR} && ./serve_local.sh 8080"
echo "⚠️ Avoid using 'python3 -m http.server' directly; it does not handle SPA routes and will 404 on deep links."
#!/usr/bin/env bash
# Build and package the Flutter web app for PRODUCTION delivery with optimizations.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
OUT_DIR="web_prod_export_${TIMESTAMP}"
ZIP_FILE="${OUT_DIR}.zip"
cd "${PROJECT_ROOT}"
write_prod_env() {
cat > assets/config/env.json <<'EOF'
{
"flavor":"pro",
"baseUrl":"https://api.mypoint.com.vn/8854/gup2start/rest",
"t3Token":"runner-env-flavor-pro",
"enableLogging":false
}
EOF
echo "📋 Production config written to assets/config/env.json"
}
copy_x_app_sdk() {
local src="node_modules/x-app-sdk/dist/index.es.js"
local dest="build/web/js/x_app_sdk_bundle.js"
if [ ! -f "${src}" ]; then
echo "❌ x-app-sdk bundle not found at ${src}" >&2
exit 1
fi
mkdir -p "$(dirname "${dest}")"
cp "${src}" "${dest}"
echo "✅ Copied x-app-sdk bundle."
}
compress_assets() {
local dir="$1"
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 {} \;
else
echo "⚠️ gzip not available, skipping .gz artifacts"
fi
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 {} \;
else
echo "⚠️ brotli not available, skipping .br artifacts"
fi
}
echo "🚀 Building PRODUCTION export (optimized)..."
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)..."
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
copy_x_app_sdk
echo "📁 Preparing export directory: ${OUT_DIR}"
mkdir -p "${OUT_DIR}"
cp -r build/web/* "${OUT_DIR}/"
if [ -f "web/firebase-messaging-sw.js" ] && [ ! -f "${OUT_DIR}/firebase-messaging-sw.js" ]; then
cp web/firebase-messaging-sw.js "${OUT_DIR}/"
fi
# Helper script for local preview with SPA fallback
cat > "${OUT_DIR}/serve_local.sh" <<'EOF'
#!/usr/bin/env bash
PORT="${1:-8080}"
python3 - "$PORT" <<'PY'
import http.server
import socketserver
import os
import sys
ROOT = os.getcwd()
PORT = int(sys.argv[1])
class SPAHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
resolved = self.translate_path(self.path)
if not os.path.exists(resolved):
self.path = '/index.html'
return super().do_GET()
def log_message(self, fmt, *args):
print(f"🌐 {fmt % args}")
with socketserver.TCPServer(('', PORT), SPAHandler) as httpd:
print(f"🚀 Serving {ROOT} at http://localhost:{PORT}")
print("💡 SPA fallback enabled. Ctrl+C to stop.")
httpd.serve_forever()
PY
EOF
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"
else
echo "ℹ️ KEEP_CANVASKIT=1 → giữ nguyên thư mục canvaskit."
fi
echo "🗜️ Precompressing assets..."
compress_assets "${OUT_DIR}"
echo "📦 Creating zip archive ${ZIP_FILE}..."
zip -rq "${ZIP_FILE}" "${OUT_DIR}"
echo ""
echo "🎉 PRODUCTION export ready!"
echo " Folder : ${OUT_DIR}"
echo " Zip : ${ZIP_FILE}"
echo ""
echo "📌 Deliverable: send the zip (or folder) to the hosting team. Serve with gzip/brotli if possible."
echo ""
echo "▶️ Quick preview command:"
echo " cd ${OUT_DIR} && ./serve_local.sh 8080"
echo "⚠️ Avoid using 'python3 -m http.server' directly; it does not handle SPA routes and will 404 on deep links."
#!/bin/bash
# Script để export Flutter web app thành HTML/CSS cho deployment
# Có thể chỉ định môi trường (mặc định: prod). Ví dụ: ./export_web.sh dev
TARGET_ENV="${1:-prod}"
ENV_LABEL=$(echo "$TARGET_ENV" | tr '[:lower:]' '[:upper:]')
echo "🚀 Exporting Flutter web app for ${ENV_LABEL} environment..."
# Kill server cũ trên port 8080 (nếu có)
echo "🛑 Stopping any existing server on :8080..."
lsof -i :8080 | awk 'NR>1 {print $2}' | xargs kill -9 2>/dev/null || true
# Switch to target environment automatically
echo "🔧 Switching to ${ENV_LABEL} environment..."
./scripts/switch_env.sh "$TARGET_ENV"
# Clear cache build để tránh dính SW/cache cũ
echo "🧹 Clearing build caches..."
flutter clean
rm -rf .dart_tool build
flutter pub get
# Install web dependencies
echo "📦 Installing web dependencies..."
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_${TARGET_ENV}_$(date +%Y%m%d_%H%M%S)"
echo "📁 Creating export directory: $EXPORT_DIR"
mkdir -p "$EXPORT_DIR"
# 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!"
echo "📥 Copying x-app-sdk bundle into build artifacts..."
./scripts/copy_x_app_sdk_bundle.sh
# Copy toàn bộ nội dung từ build/web
echo "📦 Copying web files..."
cp -r build/web/* "$EXPORT_DIR/"
# Copy firebase-messaging-sw.js nếu chưa có
if [ ! -f "$EXPORT_DIR/firebase-messaging-sw.js" ]; then
cp web/firebase-messaging-sw.js "$EXPORT_DIR/"
fi
# Tạo file test để demo URL parameters
cat > "$EXPORT_DIR/test_urls.html" << 'EOF'
<!DOCTYPE html>
<html>
<head>
<title>Test URL Parameters</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.test-link { display: block; margin: 10px 0; padding: 10px; background: #f0f0f0; text-decoration: none; color: #333; }
.test-link:hover { background: #e0e0e0; }
</style>
</head>
<body>
<h1>Test URL Parameters</h1>
<p>Click các link dưới đây để test URL parameters:</p>
<a href="?token=abc123" class="test-link">Test với token: abc123</a>
<a href="?token=abc123&userId=user456" class="test-link">Test với token + userId</a>
<a href="?token=admin789&userId=admin001" class="test-link">Test với admin token</a>
<a href="/" class="test-link">Test không có parameters (onboarding)</a>
<h2>Debug Info:</h2>
<div id="debug-info"></div>
<script>
// Hiển thị thông tin debug
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get('token');
const userId = urlParams.get('userId');
document.getElementById('debug-info').innerHTML = `
<p><strong>Current URL:</strong> ${window.location.href}</p>
<p><strong>Token:</strong> ${token || 'None'}</p>
<p><strong>UserId:</strong> ${userId || 'None'}</p>
<p><strong>localStorage url_token:</strong> ${localStorage.getItem('url_token') || 'None'}</p>
<p><strong>localStorage url_user_id:</strong> ${localStorage.getItem('url_user_id') || 'None'}</p>
`;
</script>
</body>
</html>
EOF
echo "📝 Created test_urls.html for testing"
# Tạo file README cho bên web
cat > "$EXPORT_DIR/README_DEPLOYMENT.md" << 'EOF'
# MyPoint Flutter Web App - Deployment Guide
## Cấu trúc thư mục
- `index.html` - File chính của ứng dụng
- `main.dart.js` - JavaScript code của Flutter app
- `flutter_bootstrap.js` - Flutter bootstrap script
- `canvaskit/` - Flutter rendering engine
- `assets/` - Images, fonts, và data files
- `firebase-messaging-sw.js` - Firebase service worker
## Yêu cầu server
- Web server hỗ trợ serving static files
- HTTPS được khuyến nghị cho PWA features
- CORS headers nếu cần thiết cho API calls
## Cấu hình server
1. Serve tất cả files từ thư mục này
2. Đảm bảo MIME types đúng:
- `.js` files: `application/javascript`
- `.wasm` files: `application/wasm`
- `.json` files: `application/json`
- `.png/.jpg` files: `image/*`
## Environment Variables
App sử dụng các biến môi trường từ `assets/config/env.json`
Có thể override bằng query parameters: `?env=production`
## Firebase Configuration
Firebase config được load từ `firebase_options.dart`
Đảm bảo Firebase project được cấu hình đúng cho domain này.
## 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
EOF
# Tạo file .htaccess cho Apache
cat > "$EXPORT_DIR/.htaccess" << 'EOF'
# Apache configuration for Flutter web app
# Enable compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/wasm
</IfModule>
# Set correct MIME types
AddType application/javascript .js
AddType application/wasm .wasm
AddType application/json .json
# Cache static assets
<IfModule mod_expires.c>
ExpiresActive on
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType application/wasm "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
</IfModule>
# Security headers
<IfModule mod_headers.c>
Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options DENY
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</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
cat > "$EXPORT_DIR/nginx.conf" << 'EOF'
# Nginx configuration for Flutter web app
server {
listen 80;
server_name your-domain.com;
root /path/to/web_export;
index index.html;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json application/wasm;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|wasm)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Handle Flutter routes
location / {
try_files $uri $uri/ /index.html;
}
# Security headers
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin-when-cross-origin";
# 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
# Tạo file package.json cho Node.js hosting
cat > "$EXPORT_DIR/package.json" << 'EOF'
{
"name": "mypoint-flutter-web",
"version": "1.0.0",
"description": "MyPoint Flutter Web Application",
"main": "index.html",
"scripts": {
"start": "npx serve -s . -l 3000",
"build": "echo 'Already built by Flutter'",
"serve": "npx serve -s . -l 3000"
},
"dependencies": {
"serve": "^14.0.0"
},
"engines": {
"node": ">=14.0.0"
}
}
EOF
# Tạo file docker-compose.yml cho Docker deployment
cat > "$EXPORT_DIR/docker-compose.yml" << 'EOF'
version: '3.8'
services:
mypoint-web:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- .:/usr/share/nginx/html:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
restart: unless-stopped
EOF
# Tạo file Dockerfile
cat > "$EXPORT_DIR/Dockerfile" << 'EOF'
FROM nginx:alpine
# Copy web files
COPY . /usr/share/nginx/html/
# Copy nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Expose port
EXPOSE 80
# Start nginx
CMD ["nginx", "-g", "daemon off;"]
EOF
# Tạo zip file để dễ dàng transfer
echo "📦 Creating zip package..."
zip -r "${EXPORT_DIR}.zip" "$EXPORT_DIR"
echo ""
echo "✅ Export hoàn thành!"
echo "📁 Thư mục export: $EXPORT_DIR"
echo "📦 Zip file: ${EXPORT_DIR}.zip"
echo ""
echo "🚀 Cách deploy:"
echo "1. Upload toàn bộ nội dung thư mục $EXPORT_DIR lên web server"
echo "2. Cấu hình web server theo hướng dẫn trong README_DEPLOYMENT.md"
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 ""
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 &)"
else
echo "❌ Build thất bại!"
exit 1
fi
import 'dart:async';
import 'dart:collection';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../configs/constants.dart';
......@@ -64,7 +65,7 @@ class AppLoading {
double size = 56,
double strokeWidth = 4,
}) {
print('AppLoading.show called');
debugPrint('AppLoading.show called');
// Đưa thao tác vào hàng đợi, không làm ngay
_ops.add(() {
if (isShowing) {
......@@ -109,7 +110,7 @@ class AppLoading {
}
void hide() {
print('AppLoading.hide called');
debugPrint('AppLoading.hide called');
_ops.add(() {
_timer?.cancel();
_timer = null;
......
......@@ -119,13 +119,11 @@ class AppNavigator {
bool showCloseButton = false,
VoidCallback? onConfirmed,
}) {
print("Show alert error: $_errorDialogShown");
debugPrint("Show alert error: $_errorDialogShown");
if (_errorDialogShown) return;
final context = _ctx ?? Get.context ?? Get.overlayContext;
if (context == null) {
if (kDebugMode) {
print('⚠️ AppNavigator: Unable to show alert, no context available');
}
debugPrint('⚠️ AppNavigator: Unable to show alert, no context available');
return;
}
_errorDialogShown = true;
......
......@@ -13,8 +13,8 @@ class BaseResponseModel<T> {
final T? data;
bool get isSuccess {
final _code = code ?? 0;
if (_code >= 200 && _code < 299) return true;
final rawCode = code ?? 0;
if (rawCode >= 200 && rawCode < 299) return true;
return status?.toUpperCase() == "SUCCESS";
}
......
......@@ -65,13 +65,13 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen>
/// Called when the widget is first inserted into the tree.
/// Use this to initialize data, setup listeners, etc.
void onInit() {
if (kDebugMode) print("onInit: $runtimeType");
debugPrint("onInit: $runtimeType");
}
/// Called when the widget is removed from the tree.
/// Use this to cleanup resources, cancel timers, etc.
void onDispose() {
if (kDebugMode) print("onDispose: $runtimeType");
debugPrint("onDispose: $runtimeType");
}
// MARK: - Route Visibility Methods
......@@ -79,25 +79,25 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen>
/// Called when the route is about to become visible (push or uncovered).
/// Use this to prepare data, start animations, etc.
void onRouteWillAppear() {
if (kDebugMode) print("onRouteWillAppear: $runtimeType");
debugPrint("onRouteWillAppear: $runtimeType");
}
/// Called when the route has become visible.
/// Use this to start timers, refresh data, etc.
void onRouteDidAppear() {
if (kDebugMode) print("onRouteDidAppear: $runtimeType");
debugPrint("onRouteDidAppear: $runtimeType");
}
/// Called when the route is about to be covered or popped.
/// Use this to pause operations, save state, etc.
void onRouteWillDisappear() {
if (kDebugMode) print("onRouteWillDisappear: $runtimeType");
debugPrint("onRouteWillDisappear: $runtimeType");
}
/// Called when the route has been covered or popped.
/// Use this to stop timers, cleanup temporary resources, etc.
void onRouteDidDisappear() {
if (kDebugMode) print("onRouteDidDisappear: $runtimeType");
debugPrint("onRouteDidDisappear: $runtimeType");
}
// MARK: - App Lifecycle Methods
......@@ -105,13 +105,13 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen>
/// Called when the app becomes active (foreground).
/// Use this to resume operations, refresh data, etc.
void onAppResumed() {
if (kDebugMode) print("onAppResumed: $runtimeType");
debugPrint("onAppResumed: $runtimeType");
}
/// Called when the app becomes inactive (background).
/// Use this to pause operations, save state, etc.
void onAppPaused() {
if (kDebugMode) print("onAppPaused: $runtimeType");
debugPrint("onAppPaused: $runtimeType");
}
// MARK: - UI Helper Methods
......
......@@ -3,8 +3,6 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/base/app_loading.dart';
import '../configs/constants.dart';
class BaseViewModel extends GetxController with WidgetsBindingObserver {
var isShowLoading = false;
RxBool isShowKey = false.obs;
......
......@@ -46,15 +46,15 @@ class DeviceInfo {
static DeviceDetails? _cachedDetails;
static Future<String> getDeviceId() async {
_cachedDeviceId = "13sd26fc-748f-4d1a-a064-af8d7874d565";
// if (_cachedDeviceId != null) return _cachedDeviceId!;
// final prefs = await SharedPreferences.getInstance();
// String? deviceId = prefs.getString(_deviceIdPreferenceKey);
// if (deviceId == null || deviceId.isEmpty) {
// deviceId = const Uuid().v4();
// await prefs.setString(_deviceIdPreferenceKey, deviceId);
// }
// _cachedDeviceId = deviceId;
// _cachedDeviceId = "13sd26fc-748f-4d1a-a064-af8d7874d565";
if (_cachedDeviceId != null) return _cachedDeviceId!;
final prefs = await SharedPreferences.getInstance();
String? deviceId = prefs.getString(_deviceIdPreferenceKey);
if (deviceId == null || deviceId.isEmpty) {
deviceId = const Uuid().v4();
await prefs.setString(_deviceIdPreferenceKey, deviceId);
}
_cachedDeviceId = deviceId;
return _cachedDeviceId!;
}
......@@ -82,7 +82,7 @@ class DeviceInfo {
os = 'Android';
final and = await deviceInfo.androidInfo;
final rel = and.version.release;
final sdk = and.version.sdkInt?.toString() ?? '';
final sdk = and.version.sdkInt.toString() ?? '';
osVersion = sdk.isEmpty ? rel : '$rel (SDK $sdk)';
} else {
os = Platform.operatingSystem;
......
......@@ -17,7 +17,7 @@ import 'package:mypoint_flutter_app/core/deep_link_service.dart';
class AppInitializer {
/// Initialize all core app features
static Future<void> initialize() async {
print('🚀 Initializing app...');
debugPrint('🚀 Initializing app...');
// Load environment configuration
await loadEnv();
// Initialize data preferences
......@@ -34,43 +34,43 @@ class AppInitializer {
await _initializeWebFeatures();
// Initialize deep link handlers (Branch, URI schemes)
await DeepLinkService().initialize();
print('✅ App initialization completed');
debugPrint('✅ App initialization completed');
}
/// Initialize web-specific features
static Future<void> _initializeWebFeatures() async {
if (kIsWeb) {
print('🌐 Initializing web-specific features...');
debugPrint('🌐 Initializing web-specific features...');
try {
// Initialize x-app-sdk
await webInitializeXAppSDK();
await _configureWebSdkHeader();
print('✅ Web features initialized successfully');
debugPrint('✅ Web features initialized successfully');
} catch (e) {
print('❌ Error initializing web features: $e');
debugPrint('❌ Error initializing web features: $e');
}
} else {
print('📱 Skipping web features initialization for mobile');
debugPrint('📱 Skipping web features initialization for mobile');
}
}
/// Initialize Firebase and FCM (mobile only)
static Future<void> _initializeFirebase() async {
if (!kIsWeb) {
print('📱 Initializing Firebase for mobile...');
debugPrint('📱 Initializing Firebase for mobile...');
await initFirebaseAndFcm();
} else {
print('🌐 Skipping Firebase initialization for web');
debugPrint('🌐 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...');
debugPrint('💰 Fetching user point...');
await UserPointManager().fetchUserPoint();
} else {
print('👤 User not logged in, skipping point fetch');
debugPrint('👤 User not logged in, skipping point fetch');
}
}
......@@ -80,18 +80,18 @@ class AppInitializer {
'mode': 'mini',
'iconNavigationColor': '#000000',
'navigationColor': '#FFFFFF',
'iconNavigationPosision': 'right',
// 'iconNavigationPosision': 'right',
'headerTitle': 'MyPoint',
// 'headerSubTitle': 'Tích điểm - đổi quà nhanh chóng',
// 'headerColor': '#ffffff',
'headerColor': '#E71D28',
// 'headerTextColor': '#000000',
// 'headerIcon': 'https://cdn.mypoint.vn/app_assets/mypoint_icon.png',
});
if (response != null && kDebugMode) {
print('🧭 x-app-sdk header configured: $response');
if (response != null) {
debugPrint('🧭 x-app-sdk header configured: $response');
}
} catch (error) {
print('❌ Failed to configure x-app-sdk header: $error');
debugPrint('❌ Failed to configure x-app-sdk header: $error');
}
}
......@@ -106,7 +106,7 @@ class AppInitializer {
// Handle launch from local notification tap when app was killed
handleLocalNotificationLaunchIfAny();
} catch (e) {
if (kDebugMode) print('Error in setupPostInitCallbacks: $e');
debugPrint('Error in setupPostInitCallbacks: $e');
}
}
......@@ -114,9 +114,7 @@ class AppInitializer {
static Future<void> _handleInitialNotificationLaunch() async {
try {
final initial = await FirebaseMessaging.instance.getInitialMessage();
print(
'Checking initial message for app launch from terminated state...$initial',
);
debugPrint('Checking initial message for app launch from terminated state...$initial');
if (initial == null) return;
WidgetsBinding.instance.addPostFrameCallback((_) {
Future.delayed(const Duration(seconds: 1), () {
......
......@@ -5,7 +5,6 @@ import 'package:mypoint_flutter_app/extensions/string_extension.dart';
import 'package:uni_links/uni_links.dart';
import 'package:mypoint_flutter_app/directional/directional_screen.dart';
import 'package:mypoint_flutter_app/extensions/crypto.dart' as mycrypto;
import '../directional/directional_action_type.dart';
class DeepLinkService {
......@@ -20,7 +19,7 @@ class DeepLinkService {
Future<void> initialize() async {
if (_initialized) return;
_initialized = true;
if (kDebugMode) print('🔗 Initializing DeepLinkService...');
debugPrint('🔗 Initializing DeepLinkService...');
await _initBranchSdk();
await _handleInitialLink();
......@@ -38,21 +37,15 @@ class DeepLinkService {
Future<void> _initBranchSdk() async {
try {
await FlutterBranchSdk.init(enableLogging: kDebugMode);
if (kDebugMode) {
print('🌿 Branch SDK init ');
}
debugPrint('🌿 Branch SDK init ');
_branchSub = FlutterBranchSdk.listSession().listen(
_handleBranchSession,
onError: (error) {
if (kDebugMode) {
print('❌ Branch session stream error: $error');
}
debugPrint('❌ Branch session stream error: $error');
},
);
} catch (e) {
if (kDebugMode) {
print('❌ Failed to initialize Branch SDK: $e');
}
debugPrint('❌ Failed to initialize Branch SDK: $e');
}
}
......@@ -75,7 +68,7 @@ class DeepLinkService {
// Firebase Dynamic Links removed due to version constraints.
void _routeFromUriString(String uriStr) {
if (kDebugMode) print('🔗 Deep link received: $uriStr');
debugPrint('🔗 Deep link received: $uriStr');
final uri = Uri.tryParse(uriStr);
if (uri == null) return;
......@@ -90,7 +83,7 @@ class DeepLinkService {
for (final secret in candidates) {
final phone = mycrypto.Crypto(cipherHex: cipherHex, secretKey: secret).decryption().orEmpty;
if (phone.isNotEmpty) {
if (kDebugMode) print('🔐 Decrypted phone from key: $phone');
debugPrint('🔐 Decrypted phone from key: $phone');
final direction = DirectionalScreen.buildByName(
name: DirectionalScreenName.linkMBPAccount,
clickActionParam: phone
......@@ -108,9 +101,7 @@ class DeepLinkService {
}
void _handleBranchSession(Map<dynamic, dynamic> data) {
if (kDebugMode) {
print('🌿 Branch session data: $data');
}
debugPrint('🌿 Branch session data: $data');
final dynamic clickedLink = data["+clicked_branch_link"];
if (clickedLink != true && clickedLink != "true") {
......
import 'package:flutter/material.dart';
import '../screen/flash_sale/flash_sale_screen.dart'
deferred as flash_sale show FlashSaleScreen;
import '../screen/data_network_service/data_network_service_screen.dart'
deferred as data_network_service show DataNetworkServiceScreen;
import '../screen/webview/payment_web_view_screen.dart'
deferred as payment_web_view show PaymentWebViewScreen;
import '../screen/achievement/achievement_list_screen.dart'
deferred as achievement_list show AchievementListScreen;
import '../screen/affiliate/affiliate_tab_screen.dart'
deferred as affiliate_tab show AffiliateTabScreen;
import '../screen/history_point/history_point_screen.dart'
deferred as history_point show HistoryPointScreen;
import '../screen/bank_account_manager/bank_account_manager_screen.dart'
deferred as bank_account_manager show BankAccountManagerScreen;
import '../screen/vplay_game_center/vplay_game_center_screen.dart'
deferred as vplay_game_center show VplayGameCenterScreen;
import '../screen/health_book/health_book_screen.dart'
deferred as health_book show HealthBookScreen;
import '../screen/invite_friend_campaign/invite_friend_campaign_screen.dart'
deferred as invite_friend show InviteFriendCampaignScreen;
import '../screen/quiz_campaign/quiz_campaign_screen.dart'
deferred as survey_question show SurveyQuestionScreen;
import '../screen/device_manager/device_manager_screen.dart'
deferred as device_manager show DeviceManagerScreen;
import '../screen/order_menu/order_menu_screen.dart'
deferred as order_menu show OrderMenuScreen;
import '../screen/location_address/location_address_screen.dart'
deferred as location_address show LocationAddressScreen;
import '../screen/membership/membership_screen.dart'
deferred as membership show MembershipScreen;
import '../screen/mobile_card/product_mobile_card_screen.dart'
deferred as product_mobile_card show ProductMobileCardScreen;
import '../screen/history_point_cashback/history_point_cashback_screen.dart'
deferred as history_point_cashback show HistoryPointCashBackScreen;
import '../screen/electric_payment/electric_payment_screen.dart'
deferred as electric_payment show ElectricPaymentScreen;
import '../screen/electric_payment/electric_payment_history_screen.dart'
deferred as electric_payment_history show ElectricPaymentHistoryScreen;
import '../screen/traffic_service/traffic_service_screen.dart'
deferred as traffic_service show TrafficServiceScreen;
import '../screen/traffic_service/traffic_service_detail_screen.dart'
deferred as traffic_service_detail show TrafficServiceDetailScreen;
import '../screen/campaign7day/campaign_7day_screen.dart'
deferred as campaign_7day show Campaign7DayScreen;
import '../screen/daily_checkin/daily_checkin_screen.dart'
deferred as daily_checkin show DailyCheckInScreen;
import '../screen/transaction/transactions_history_screen.dart'
deferred as transactions_history show TransactionHistoryScreen;
import '../screen/news/news_list_screen.dart'
deferred as news_list show NewsListScreen;
import '../screen/qr_code/qr_code_screen.dart'
deferred as qr_code show QRCodeScreen;
import '../screen/affiliate_brand_detail/affiliate_brand_detail_screen.dart'
deferred as affiliate_brand_detail show AffiliateBrandDetailScreen;
import '../screen/affiliate_brand_detail/affiliate_brand_list_screen.dart'
deferred as affiliate_brand_list show AffiliateBrandListScreen;
import '../screen/affiliate_brand_detail/affiliate_category_grid_screen.dart'
deferred as affiliate_category_grid show AffiliateCategoryGridScreen;
import '../screen/contacts/contacts_list_screen.dart'
deferred as contacts_list show ContactsListScreen;
import '../screen/health_book/health_book_card_detail.dart'
deferred as health_book_card_detail show HealthBookCardDetail;
import '../screen/interested_categories/interestied_categories_screen.dart'
deferred as interest_categories show InterestCategoriesScreen;
import '../screen/support/support_screen.dart'
deferred as support show SupportScreen;
import '../screen/topup/topup_screen.dart'
deferred as topup show PhoneTopUpScreen;
import '../screen/transaction/history/transaction_history_detail_screen.dart'
deferred as transaction_history_detail show TransactionHistoryDetailScreen;
import '../screen/transaction/transaction_detail_screen.dart'
deferred as transaction_detail show TransactionDetailScreen;
import '../screen/voucher/detail/voucher_detail_screen.dart'
deferred as voucher_detail show VoucherDetailScreen;
import '../screen/voucher/mobile_card/my_mobile_card_detail_widget.dart'
deferred as mobile_card_detail show MyMobileCardDetailScreen;
import '../screen/voucher/mobile_card/my_mobile_card_list_widget.dart'
deferred as mobile_card_list show MyMobileCardListScreen;
import '../screen/voucher/my_voucher/my_product_list_widget.dart'
deferred as my_voucher_list show MyVoucherListScreen;
typedef DeferredScreenBuilder = Widget Function(BuildContext context);
/// Generic widget that handles loading a deferred library before rendering
/// the actual screen. This keeps the initial bundle smaller and only loads
/// heavy features when the user needs them.
class DeferredScreen extends StatefulWidget {
const DeferredScreen({
super.key,
required this.loadLibrary,
required this.builder,
this.errorMessage,
});
final Future<void> Function() loadLibrary;
final DeferredScreenBuilder builder;
final String? errorMessage;
@override
State<DeferredScreen> createState() => DeferredScreenState();
}
class DeferredScreenState extends State<DeferredScreen> {
late final Future<void> _loadFuture;
@override
void initState() {
super.initState();
_loadFuture = widget.loadLibrary();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<void>(
future: _loadFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return widget.builder(context);
}
if (snapshot.hasError) {
return _DeferredError(
message: widget.errorMessage,
error: snapshot.error,
);
}
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
},
);
}
}
class _DeferredError extends StatelessWidget {
const _DeferredError({this.message, this.error});
final String? message;
final Object? error;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(),
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
size: 48,
color: theme.colorScheme.error,
),
const SizedBox(height: 16),
Text(
message ?? 'Không thể tải màn hình này.',
textAlign: TextAlign.center,
style: theme.textTheme.titleMedium,
),
if (error != null) ...[
const SizedBox(height: 12),
Text(
'$error',
textAlign: TextAlign.center,
style: theme.textTheme.bodySmall,
),
],
],
),
),
),
);
}
}
class FlashSaleDeferredScreen extends StatelessWidget {
const FlashSaleDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: flash_sale.loadLibrary,
builder: (_) => flash_sale.FlashSaleScreen(),
errorMessage: 'Không thể tải Flash Sale.',
);
}
}
class DataNetworkServiceDeferredScreen extends StatelessWidget {
const DataNetworkServiceDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: data_network_service.loadLibrary,
builder: (_) => data_network_service.DataNetworkServiceScreen(),
errorMessage: 'Không thể tải dịch vụ data.',
);
}
}
class PaymentWebViewDeferredScreen extends StatelessWidget {
const PaymentWebViewDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: payment_web_view.loadLibrary,
builder: (_) => payment_web_view.PaymentWebViewScreen(),
errorMessage: 'Không thể tải trang thanh toán.',
);
}
}
class AffiliateTabDeferredScreen extends StatelessWidget {
const AffiliateTabDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: affiliate_tab.loadLibrary,
builder: (_) => affiliate_tab.AffiliateTabScreen(),
errorMessage: 'Không thể tải chiến dịch Affiliate.',
);
}
}
class HistoryPointDeferredScreen extends StatelessWidget {
const HistoryPointDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: history_point.loadLibrary,
builder: (_) => history_point.HistoryPointScreen(),
errorMessage: 'Không thể tải lịch sử điểm.',
);
}
}
class BankAccountManagerDeferredScreen extends StatelessWidget {
const BankAccountManagerDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: bank_account_manager.loadLibrary,
builder: (_) => bank_account_manager.BankAccountManagerScreen(),
errorMessage: 'Không thể tải quản lý tài khoản ngân hàng.',
);
}
}
class VplayGameCenterDeferredScreen extends StatelessWidget {
const VplayGameCenterDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: vplay_game_center.loadLibrary,
builder: (_) => vplay_game_center.VplayGameCenterScreen(),
errorMessage: 'Không thể tải Vplay Game Center.',
);
}
}
class HealthBookDeferredScreen extends StatelessWidget {
const HealthBookDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: health_book.loadLibrary,
builder: (_) => health_book.HealthBookScreen(),
errorMessage: 'Không thể tải Sổ sức khỏe.',
);
}
}
class SurveyQuestionDeferredScreen extends StatelessWidget {
const SurveyQuestionDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: survey_question.loadLibrary,
builder: (_) => survey_question.SurveyQuestionScreen(),
errorMessage: 'Không thể tải khảo sát.',
);
}
}
class DeviceManagerDeferredScreen extends StatelessWidget {
const DeviceManagerDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: device_manager.loadLibrary,
builder: (_) => device_manager.DeviceManagerScreen(),
errorMessage: 'Không thể tải quản lý thiết bị.',
);
}
}
class AchievementListDeferredScreen extends StatelessWidget {
const AchievementListDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: achievement_list.loadLibrary,
builder: (_) => achievement_list.AchievementListScreen(),
errorMessage: 'Không thể tải thành tích.',
);
}
}
class InviteFriendDeferredScreen extends StatelessWidget {
const InviteFriendDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: invite_friend.loadLibrary,
builder: (_) => invite_friend.InviteFriendCampaignScreen(),
errorMessage: 'Không thể tải chiến dịch giới thiệu.',
);
}
}
class OrderMenuDeferredScreen extends StatelessWidget {
const OrderMenuDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: order_menu.loadLibrary,
builder: (_) => order_menu.OrderMenuScreen(),
errorMessage: 'Không thể tải đặt món.',
);
}
}
class LocationAddressDeferredScreen extends StatelessWidget {
const LocationAddressDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: location_address.loadLibrary,
builder: (_) => location_address.LocationAddressScreen(),
errorMessage: 'Không thể tải địa chỉ.',
);
}
}
class MembershipDeferredScreen extends StatelessWidget {
const MembershipDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: membership.loadLibrary,
builder: (_) => membership.MembershipScreen(),
errorMessage: 'Không thể tải hạng thành viên.',
);
}
}
class ProductMobileCardDeferredScreen extends StatelessWidget {
const ProductMobileCardDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: product_mobile_card.loadLibrary,
builder: (_) => product_mobile_card.ProductMobileCardScreen(),
errorMessage: 'Không thể tải thẻ điện thoại.',
);
}
}
class HistoryPointCashBackDeferredScreen extends StatelessWidget {
const HistoryPointCashBackDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: history_point_cashback.loadLibrary,
builder: (_) => history_point_cashback.HistoryPointCashBackScreen(),
errorMessage: 'Không thể tải hoàn điểm.',
);
}
}
class ElectricPaymentDeferredScreen extends StatelessWidget {
const ElectricPaymentDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: electric_payment.loadLibrary,
builder: (_) => electric_payment.ElectricPaymentScreen(),
errorMessage: 'Không thể tải thanh toán điện.',
);
}
}
class ElectricPaymentHistoryDeferredScreen extends StatelessWidget {
const ElectricPaymentHistoryDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: electric_payment_history.loadLibrary,
builder: (_) => electric_payment_history.ElectricPaymentHistoryScreen(),
errorMessage: 'Không thể tải lịch sử điện.',
);
}
}
class TrafficServiceDeferredScreen extends StatelessWidget {
const TrafficServiceDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: traffic_service.loadLibrary,
builder: (_) => traffic_service.TrafficServiceScreen(),
errorMessage: 'Không thể tải dịch vụ giao thông.',
);
}
}
class TrafficServiceDetailDeferredScreen extends StatelessWidget {
const TrafficServiceDetailDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: traffic_service_detail.loadLibrary,
builder: (_) => traffic_service_detail.TrafficServiceDetailScreen(),
errorMessage: 'Không thể tải chi tiết giao thông.',
);
}
}
class Campaign7DayDeferredScreen extends StatelessWidget {
const Campaign7DayDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: campaign_7day.loadLibrary,
builder: (_) => campaign_7day.Campaign7DayScreen(),
errorMessage: 'Không thể tải chiến dịch 7 ngày.',
);
}
}
class DailyCheckInDeferredScreen extends StatelessWidget {
const DailyCheckInDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: daily_checkin.loadLibrary,
builder: (_) => daily_checkin.DailyCheckInScreen(),
errorMessage: 'Không thể tải điểm danh hằng ngày.',
);
}
}
class TransactionHistoryDeferredScreen extends StatelessWidget {
const TransactionHistoryDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: transactions_history.loadLibrary,
builder: (_) => transactions_history.TransactionHistoryScreen(),
errorMessage: 'Không thể tải lịch sử giao dịch.',
);
}
}
class NewsListDeferredScreen extends StatelessWidget {
const NewsListDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: news_list.loadLibrary,
builder: (_) => news_list.NewsListScreen(),
errorMessage: 'Không thể tải tin tức.',
);
}
}
class QRCodeDeferredScreen extends StatelessWidget {
const QRCodeDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: qr_code.loadLibrary,
builder: (_) => qr_code.QRCodeScreen(),
errorMessage: 'Không thể mở QR Code.',
);
}
}
class AffiliateBrandDetailDeferredScreen extends StatelessWidget {
const AffiliateBrandDetailDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: affiliate_brand_detail.loadLibrary,
builder: (_) => affiliate_brand_detail.AffiliateBrandDetailScreen(),
errorMessage: 'Không thể tải chi tiết thương hiệu.',
);
}
}
class AffiliateBrandListDeferredScreen extends StatelessWidget {
const AffiliateBrandListDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: affiliate_brand_list.loadLibrary,
builder: (_) => affiliate_brand_list.AffiliateBrandListScreen(),
errorMessage: 'Không thể tải danh sách thương hiệu.',
);
}
}
class AffiliateCategoryGridDeferredScreen extends StatelessWidget {
const AffiliateCategoryGridDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: affiliate_category_grid.loadLibrary,
builder: (_) => affiliate_category_grid.AffiliateCategoryGridScreen(),
errorMessage: 'Không thể tải danh mục thương hiệu.',
);
}
}
class ContactsListDeferredScreen extends StatelessWidget {
const ContactsListDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: contacts_list.loadLibrary,
builder: (_) => contacts_list.ContactsListScreen(),
errorMessage: 'Không thể tải danh bạ.',
);
}
}
class HealthBookCardDetailDeferredScreen extends StatelessWidget {
const HealthBookCardDetailDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: health_book_card_detail.loadLibrary,
builder: (_) => health_book_card_detail.HealthBookCardDetail(),
errorMessage: 'Không thể tải chi tiết sổ sức khỏe.',
);
}
}
class InterestCategoriesDeferredScreen extends StatelessWidget {
const InterestCategoriesDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: interest_categories.loadLibrary,
builder: (_) => interest_categories.InterestCategoriesScreen(),
errorMessage: 'Không thể tải danh mục quan tâm.',
);
}
}
class SupportDeferredScreen extends StatelessWidget {
const SupportDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: support.loadLibrary,
builder: (_) => support.SupportScreen(),
errorMessage: 'Không thể tải hỗ trợ.',
);
}
}
class PhoneTopUpDeferredScreen extends StatelessWidget {
const PhoneTopUpDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: topup.loadLibrary,
builder: (_) => topup.PhoneTopUpScreen(),
errorMessage: 'Không thể tải nạp điện thoại.',
);
}
}
class TransactionDetailDeferredScreen extends StatelessWidget {
const TransactionDetailDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: transaction_detail.loadLibrary,
builder: (_) => transaction_detail.TransactionDetailScreen(),
errorMessage: 'Không thể tải chi tiết giao dịch.',
);
}
}
class TransactionHistoryDetailDeferredScreen extends StatelessWidget {
const TransactionHistoryDetailDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: transaction_history_detail.loadLibrary,
builder: (_) => transaction_history_detail.TransactionHistoryDetailScreen(),
errorMessage: 'Không thể tải chi tiết lịch sử giao dịch.',
);
}
}
class VoucherDetailDeferredScreen extends StatelessWidget {
const VoucherDetailDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: voucher_detail.loadLibrary,
builder: (_) => voucher_detail.VoucherDetailScreen(),
errorMessage: 'Không thể tải chi tiết voucher.',
);
}
}
class MyMobileCardListDeferredScreen extends StatelessWidget {
const MyMobileCardListDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: mobile_card_list.loadLibrary,
builder: (_) => mobile_card_list.MyMobileCardListScreen(),
errorMessage: 'Không thể tải thẻ điện thoại của tôi.',
);
}
}
class MyMobileCardDetailDeferredScreen extends StatelessWidget {
const MyMobileCardDetailDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: mobile_card_detail.loadLibrary,
builder: (_) => mobile_card_detail.MyMobileCardDetailScreen(),
errorMessage: 'Không thể tải chi tiết thẻ điện thoại.',
);
}
}
class MyVoucherListDeferredScreen extends StatelessWidget {
const MyVoucherListDeferredScreen({super.key});
@override
Widget build(BuildContext context) {
return DeferredScreen(
loadLibrary: my_voucher_list.loadLibrary,
builder: (_) => my_voucher_list.MyVoucherListScreen(),
errorMessage: 'Không thể tải ưu đãi của tôi.',
);
}
}
import '../shared/router_gage.dart';
enum DirectionalScreenName {
flashSale,
searchProduct,
......
......@@ -35,11 +35,14 @@ class DirectionalScreen {
DirectionalScreen._({this.clickActionType, this.clickActionParam, this.popup});
factory DirectionalScreen.fromJson(Map<String, dynamic> json) => DirectionalScreen._(
clickActionType: json[Defines.actionType] as String?,
clickActionParam: json[Defines.actionParams] as String?,
clickActionType: json['click_action_type'] as String?,
clickActionParam: json['click_action_param'] as String?,
);
Map<String, dynamic> toJson() => {Defines.actionType: clickActionType, Defines.actionParams: clickActionParam};
Map<String, dynamic> toJson() => {
'click_action_type': clickActionType,
'click_action_param': clickActionParam,
};
static DirectionalScreen? build({String? clickActionType, String? clickActionParam}) {
if ((clickActionType ?? "").isEmpty) return null;
......@@ -58,13 +61,8 @@ class DirectionalScreen {
bool begin() {
final type = DirectionalScreenNameExtension.fromRawValue(clickActionType ?? "");
if (type == null) {
print("Không nhận diện được action type: $clickActionType");
return false;
}
switch (type) {
case DirectionalScreenName.flashSale:
if ((clickActionParam ?? '').isEmpty) return false;
Get.toNamed(flashSaleScreen, arguments: {"groupId": clickActionParam});
return true;
case DirectionalScreenName.brand:
......@@ -152,9 +150,11 @@ class DirectionalScreen {
final body = Uri.encodeComponent(contentDecoded);
// iOS: &body=..., Android: ?body=...
final isIOS = defaultTargetPlatform == TargetPlatform.iOS;
final urlStr = isIOS ? 'sms:$phone&body=$body' : 'sms:$phone?body=$body';
final urlStr = isIOS
? 'sms:$phone&body=$body'
: 'sms:$phone?body=$body';
final uri = Uri.parse(urlStr);
print('Mở SMS: $uri phone=$phone, content=$content');
debugPrint('Mở SMS: $uri phone=$phone, content=$content');
_openUrlExternally(uri);
return false;
case DirectionalScreenName.setting:
......@@ -170,13 +170,15 @@ class DirectionalScreen {
BaseWebViewInput input = BaseWebViewInput(url: clickActionParam ?? "");
Get.toNamed(baseWebViewScreen, arguments: input);
return true;
case DirectionalScreenName.website:
case DirectionalScreenName.website|| DirectionalScreenName.introduction:
Get.toNamed(campaignDetailScreen, arguments: {"id": clickActionParam ?? ""});
return true;
case DirectionalScreenName.gameCenter:
Get.toNamed(vplayGameCenterScreen);
return true;
case DirectionalScreenName.viewAllVoucher:
case DirectionalScreenName.viewAllVoucher ||
DirectionalScreenName.searchProduct ||
DirectionalScreenName.productVoucher:
Get.toNamed(vouchersScreen, arguments: {"enableSearch": true});
return true;
case DirectionalScreenName.news:
......@@ -307,7 +309,7 @@ class DirectionalScreen {
if ((clickActionParam ?? '').isEmpty) return false;
_handleLinkMBPAccount();
return true;
case DirectionalScreenName.notifications:
case DirectionalScreenName.notifications:
Get.toNamed(notificationScreen);
return true;
case DirectionalScreenName.notification:
......@@ -342,7 +344,11 @@ class DirectionalScreen {
if ((clickActionParam ?? '').isEmpty) return false;
Get.toNamed(affiliateBrandDetailScreen, arguments: {"brandId": clickActionParam});
return true;
case DirectionalScreenName.viewGift ||
// case DirectionalScreenName.historyInvitedFriend:
// case DirectionalScreenName.screenAddInvitationCode:
// Deprecated / unsupported entries kept in enum for backward compatibility.
case DirectionalScreenName.flashSale ||
DirectionalScreenName.viewGift ||
DirectionalScreenName.feedback ||
DirectionalScreenName.ranking ||
DirectionalScreenName.inputReferralCode ||
......@@ -361,13 +367,13 @@ class DirectionalScreen {
_logUnsupported(type);
return false;
default:
print("Không nhận diện được action type: $clickActionType");
debugPrint("Không nhận diện được action type: $clickActionType");
return false;
}
}
void _logUnsupported(DirectionalScreenName type) {
print("⚠️ Chưa hỗ trợ điều hướng action type: ${type.rawValue}");
void _logUnsupported(type) {
debugPrint("⚠️ DirectionalScreen action type không được hỗ trợ: ${type.rawValue}");
}
void _handleLinkMBPAccount() {
......@@ -390,7 +396,7 @@ class DirectionalScreen {
onPressed: () async {
Get.back();
_gotoLoginScreen(phone, password);
print("Đồng ý đăng xuất để liên kết tài khoản $phone");
debugPrint("Đồng ý đăng xuất để liên kết tài khoản $phone");
},
bgColor: BaseColor.primary500,
textColor: Colors.white,
......@@ -406,17 +412,10 @@ class DirectionalScreen {
await DataPreference.instance.clearData();
Get.offAllNamed(loginScreen, arguments: {"phone": phone, 'password': password});
}
Future<void> openSystemSettings() async {
final opened = await openAppSettings();
if (!opened) {
debugPrint('⚠️ Không mở được trang cài đặt hệ thống');
}
}
}
Future<bool> forceOpen({required Uri url, LaunchMode mode = LaunchMode.platformDefault}) async {
print("force open ${url.toString()}");
debugPrint("force open ${url.toString()}");
if (await canLaunchUrl(url)) {
await launchUrl(
url,
......@@ -461,7 +460,10 @@ Future<bool> _safeOpenUrl(Uri url, {LaunchMode preferred = LaunchMode.platformDe
final ok = await launchUrl(
url,
mode: mode,
webViewConfiguration: const WebViewConfiguration(enableJavaScript: true, headers: <String, String>{}),
webViewConfiguration: const WebViewConfiguration(
enableJavaScript: true,
headers: <String, String>{},
),
);
if (ok) return true;
} catch (_) {
......
......@@ -25,9 +25,7 @@ class AppConfig {
if ((flavor ?? '').isEmpty) throw const FormatException("env thiếu 'flavor'");
if ((baseUrl ?? '').isEmpty) throw const FormatException("env thiếu 'baseUrl'");
// if ((token ?? '').isEmpty) throw const FormatException("env thiếu 'libToken'");
if (kDebugMode) {
print('AppConfig: flavor=$flavor, baseUrl=$baseUrl, t3Token=${token ?? ''}, enableLogging=$enable');
}
debugPrint('AppConfig: flavor=$flavor, baseUrl=$baseUrl, t3Token=${token ?? ''}, enableLogging=$enable');
return AppConfig(
flavor: flavor!,
baseUrl: baseUrl!,
......@@ -57,9 +55,7 @@ Future<void> loadEnv() async {
throw Exception('Không tải được cấu hình môi trường (env).');
}
AppConfig.current = AppConfig.fromMap(cfg);
if (kDebugMode) {
print('✅ AppConfig loaded: flavor=${AppConfig.current.flavor}, baseUrl=${AppConfig.current.baseUrl}');
}
debugPrint('✅ AppConfig loaded: flavor=${AppConfig.current.flavor}, baseUrl=${AppConfig.current.baseUrl}');
}
Future<Map<String, dynamic>?> _tryLoadAsset(String path) async {
......
import 'dart:convert';
import 'dart:typed_data';
import 'package:encrypt/encrypt.dart' as enc;
import 'package:flutter/cupertino.dart';
import 'package:pointycastle/api.dart'
show KeyParameter, PaddedBlockCipher, PaddedBlockCipherParameters;
import 'package:pointycastle/block/aes_fast.dart';
import 'package:pointycastle/block/modes/ecb.dart';
import 'package:pointycastle/padded_block_cipher/padded_block_cipher_impl.dart';
import 'package:pointycastle/paddings/pkcs7.dart';
/// Giống Swift:
/// - `cipherHex`: chuỗi hex của dữ liệu đã mã hoá AES
......@@ -16,17 +23,15 @@ class Crypto {
final keyBytes = _normalizeKeyUtf8(secretKey, 16); // AES-128 = 16 bytes
final dataBytes = _hexToBytes(cipherHex);
final key = enc.Key(keyBytes);
final aes = enc.AES(key, mode: enc.AESMode.ecb, padding: 'PKCS7');
final encrypter = enc.Encrypter(aes);
final decrypted = encrypter.decrypt(enc.Encrypted(dataBytes));
final cipher = _createCipher(keyBytes);
final decryptedBytes = cipher.process(dataBytes);
final decrypted = utf8.decode(decryptedBytes);
// ignore: avoid_print
print('Decrypted Text: $decrypted');
debugPrint('Decrypted Text: $decrypted');
return decrypted;
} catch (e) {
// ignore: avoid_print
print('Decryption failed: $e');
debugPrint('Decryption failed: $e');
return null;
}
}
......@@ -54,4 +59,17 @@ class Crypto {
out.setRange(0, raw.length, raw);
return out;
}
PaddedBlockCipher _createCipher(Uint8List key) {
final engine = ECBBlockCipher(AESFastEngine());
final cipher = PaddedBlockCipherImpl(PKCS7Padding(), engine);
cipher.init(
false,
PaddedBlockCipherParameters<KeyParameter, Null>(
KeyParameter(key),
null,
),
);
return cipher;
}
}
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart' as intl;
......@@ -81,7 +82,7 @@ extension StringDateExtension on String {
try {
return DateTime.parse(this); // 🚀 Dùng Dart core parse luôn đúng
} catch (e) {
print('❌ Date parse failed for "$this": $e');
debugPrint('❌ Date parse failed for "$this": $e');
return null;
}
}
......@@ -91,7 +92,7 @@ extension StringDateExtension on String {
try {
return intl.DateFormat(format).parseStrict(this);
} catch (e) {
print('❌ Date parse failed for "$this" with format "$format": $e');
debugPrint('❌ Date parse failed for "$this" with format "$format": $e');
return null;
}
}
......@@ -119,7 +120,7 @@ extension ParseInt on String {
final doubleValue = double.parse(this);
return doubleValue % 1 == 0 ? doubleValue.toInt() : doubleValue;
} catch (e) {
print('❌ String to Int parse failed for "$this": $e');
debugPrint('❌ String to Int parse failed for "$this": $e');
return 0;
}
}
......
......@@ -12,10 +12,8 @@ class NotificationRouter {
}
static Future<void> handleDirectionNotification(PushNotification notification) async {
if (kDebugMode) {
print('Parsed action type: ${notification.screenDirectional?.clickActionType}');
print('Parsed action param: ${notification.screenDirectional?.clickActionParam}');
}
debugPrint('Parsed action type: ${notification.screenDirectional?.clickActionType}');
debugPrint('Parsed action param: ${notification.screenDirectional?.clickActionParam}');
notification.screenDirectional?.begin();
}
}
......@@ -50,40 +48,36 @@ class PushNotification {
String? get id => _asString(info['notification_id']);
DirectionalScreen? get screenDirectional {
if (kDebugMode) {
print('=== PARSING NOTIFICATION DATA ===');
print('Raw info: $info');
}
debugPrint('=== PARSING NOTIFICATION DATA ===');
debugPrint('Raw info: $info');
String? name;
String? param;
final extra = _asMap(info['app_extra']);
if (kDebugMode) print('App extra: $extra');
debugPrint('App extra: $extra');
final screenData = _asMap(extra?['screenData']);
if (kDebugMode) print('Screen data: $screenData');
debugPrint('Screen data: $screenData');
if (screenData != null) {
name = _asString(screenData[Defines.actionType]);
param = _asString(screenData[Defines.actionParams]);
if (kDebugMode) print('From screenData - name: $name, param: $param');
debugPrint('From screenData - name: $name, param: $param');
} else {
name = _asString(info[Defines.actionType]);
param = _asString(info[Defines.actionParams]);
if (kDebugMode) print('From info - name: $name, param: $param');
debugPrint('From info - name: $name, param: $param');
}
DirectionalScreen? screen;
if (name != null || param != null) {
if (kDebugMode) print('Building DirectionalScreen with name: $name, param: $param');
debugPrint('Building DirectionalScreen with name: $name, param: $param');
screen = DirectionalScreen.build(clickActionType: name, clickActionParam: param);
}
if (kDebugMode) {
print('No action data found, using default notification screen with ID: $id');
print('Title: $title, Body: $body');
}
debugPrint('No action data found, using default notification screen with ID: $id');
debugPrint('Title: $title, Body: $body');
screen ??= DirectionalScreen.buildByName(name: DirectionalScreenName.notification, clickActionParam: id);
screen?.extraData = {
'title': title,
......
......@@ -11,7 +11,7 @@ import 'notification_parse_payload.dart';
@pragma('vm:entry-point') // bắt buộc cho background isolate
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
print('_firebaseMessagingBackgroundHandler ${message.toMap()}');
debugPrint('_firebaseMessagingBackgroundHandler ${message.toMap()}');
// Android: data-only message sẽ không tự hiển thị. Tự show local notification
if (!kIsWeb && Platform.isAndroid) {
......@@ -68,7 +68,7 @@ Future<void> _initLocalNotifications() async {
await _flnp.initialize(
init,
onDidReceiveNotificationResponse: (response) {
print('Response: $response, payload: ${response.payload}');
debugPrint('Response: $response, payload: ${response.payload}');
final info = parseNotificationPayload(response.payload);
NotificationRouter.handleDirectionNotification(PushNotification(info: info));
},
......@@ -113,13 +113,9 @@ Future<void> initFirebaseAndFcm() async {
// Tắt auto init để tránh plugin tự động thao tác permission trên web
try {
await FirebaseMessaging.instance.setAutoInitEnabled(false);
if (kDebugMode) {
print('Web: FCM auto-init disabled');
}
debugPrint('Web: FCM auto-init disabled');
} catch (_) {}
if (kDebugMode) {
print('Web: skip requesting notification permission at startup');
}
debugPrint('Web: skip requesting notification permission at startup');
} else if (Platform.isIOS) {
await messaging.requestPermission(alert: true, badge: true, sound: true);
} else {
......@@ -128,9 +124,7 @@ Future<void> initFirebaseAndFcm() async {
await _initLocalNotifications();
} catch (e) {
if (kDebugMode) {
print('Firebase initialization error: $e');
}
debugPrint('Firebase initialization error: $e');
// Continue without Firebase on web if there's an error
if (kIsWeb) {
return;
......@@ -140,12 +134,10 @@ Future<void> initFirebaseAndFcm() async {
// Foreground: Android không tự hiển thị -> ta show local notification
FirebaseMessaging.onMessage.listen((message) {
if (kDebugMode) {
print('=== FOREGROUND MESSAGE RECEIVED ===');
print('Message: ${message.messageId}');
print('Data: ${message.data}');
print('Notification: ${message.notification?.title} - ${message.notification?.body}');
}
debugPrint('=== FOREGROUND MESSAGE RECEIVED ===');
debugPrint('Message: ${message.messageId}');
debugPrint('Data: ${message.data}');
debugPrint('Notification: ${message.notification?.title} - ${message.notification?.body}');
// Only show local notifications on mobile platforms, not web
if (!kIsWeb) {
final n = message.notification;
......@@ -178,13 +170,9 @@ Future<void> initFirebaseAndFcm() async {
if (messaging != null && !kIsWeb) {
try {
final token = await messaging.getToken();
if (kDebugMode) {
print('FCM token: $token');
}
debugPrint('FCM token: $token');
} catch (e) {
if (kDebugMode) {
print('Error getting FCM token: $e');
}
debugPrint('Error getting FCM token: $e');
}
}
}
......@@ -193,9 +181,7 @@ Future<void> initFirebaseAndFcm() async {
Future<AuthorizationStatus?> requestWebNotificationPermission() async {
// ĐÃ TẮT CHO WEB: Không request permission trên web
if (kIsWeb) {
if (kDebugMode) {
print('Web notifications disabled: skip requesting permission');
}
debugPrint('Web notifications disabled: skip requesting permission');
return AuthorizationStatus.denied;
}
return null;
......@@ -208,21 +194,15 @@ Future<String?> getFcmTokenIfAvailable() async {
if (kIsWeb) {
final settings = await messaging.getNotificationSettings();
if (settings.authorizationStatus != AuthorizationStatus.authorized) {
if (kDebugMode) {
print('Web notifications not authorized. Cannot retrieve FCM token.');
}
debugPrint('Web notifications not authorized. Cannot retrieve FCM token.');
return null;
}
}
final token = await messaging.getToken();
if (kDebugMode) {
print('FCM token (if available): $token');
}
debugPrint('FCM token (if available): $token');
return token;
} catch (e) {
if (kDebugMode) {
print('Error retrieving FCM token: $e');
}
debugPrint('Error retrieving FCM token: $e');
return null;
}
}
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