Commit 6b980613 authored by DatHV's avatar DatHV
Browse files

update project structure

parent bfff9e47
import 'dart:async'; import 'dart:async';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:mypoint_flutter_app/base/app_loading.dart'; import 'package:mypoint_flutter_app/shared/widgets/loading/app_loading.dart';
import '../../base/app_navigator.dart'; import '../../../app/routing/app_navigator.dart';
import '../dio_http_service.dart';
import '../error_mapper.dart'; import '../error_mapper.dart';
import '../dio_http_service.dart';
class ExceptionInterceptor extends Interceptor { class ExceptionInterceptor extends Interceptor {
static Completer<bool>? _currentRetryPrompt; static Completer<bool>? _currentRetryPrompt;
......
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart'; import 'package:mypoint_flutter_app/shared/preferences/data_preference.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class RequestInterceptor extends Interceptor { class RequestInterceptor extends Interceptor {
......
import 'dart:convert'; import 'dart:convert';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:mypoint_flutter_app/base/base_response_model.dart'; import 'package:mypoint_flutter_app/shared/widgets/base_view/base_response_model.dart';
import '../configs/callbacks.dart'; import '../../app/config/callbacks.dart';
import '../configs/constants.dart'; import '../../app/config/constants.dart';
enum Method { GET, POST, PUT, DELETE } enum Method { GET, POST, PUT, DELETE }
......
import 'dart:async'; import 'dart:async';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client.dart'; import 'package:mypoint_flutter_app/core/network/restful_api_client.dart';
import '../base/base_response_model.dart'; import '../../shared/widgets/base_view/base_response_model.dart';
import '../base/base_view_model.dart'; import '../../shared/widgets/base_view/base_view_model.dart';
import '../configs/constants.dart'; import '../../app/config/constants.dart';
import '../base/app_navigator.dart'; import '../../app/routing/app_navigator.dart';
import 'interceptor/network_error_gate.dart';
import 'dio_extra_keys.dart'; import 'dio_extra_keys.dart';
import 'dio_http_service.dart'; import 'dio_http_service.dart';
import 'error_mapper.dart'; import 'error_mapper.dart';
import 'interceptor/network_error_gate.dart';
typedef ApiCall<T> = Future<BaseResponseModel<T>> Function(); typedef ApiCall<T> = Future<BaseResponseModel<T>> Function();
typedef OnSuccess<T> = typedef OnSuccess<T> =
...@@ -33,9 +33,11 @@ class RestfulApiViewModel extends BaseViewModel { ...@@ -33,9 +33,11 @@ class RestfulApiViewModel extends BaseViewModel {
OnComplete? onComplete, OnComplete? onComplete,
bool showAppNavigatorDialog = false, bool showAppNavigatorDialog = false,
bool withLoading = true, bool withLoading = true,
bool trackLoading = true,
String defaultError = ErrorCodes.commonError, String defaultError = ErrorCodes.commonError,
}) async { }) async {
if (withLoading) showLoading(); if (withLoading) showLoading();
if (trackLoading) beginTrackedLoading();
BaseResponseModel<T>? res; BaseResponseModel<T>? res;
try { try {
res = await request(); res = await request();
...@@ -86,6 +88,7 @@ class RestfulApiViewModel extends BaseViewModel { ...@@ -86,6 +88,7 @@ class RestfulApiViewModel extends BaseViewModel {
} }
} finally { } finally {
if (withLoading) hideLoading(); if (withLoading) hideLoading();
if (trackLoading) endTrackedLoading();
onComplete?.call(); onComplete?.call();
} }
} }
......
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:local_auth/local_auth.dart'; import 'package:local_auth/local_auth.dart';
import '../resources/base_color.dart'; import '../theme/base_color.dart';
import '../widgets/alert/custom_alert_dialog.dart'; import '../../shared/widgets/alert/custom_alert_dialog.dart';
import '../widgets/alert/data_alert_model.dart'; import '../../shared/widgets/alert/data_alert_model.dart';
enum BiometricTypeEnum { enum BiometricTypeEnum {
none, none,
......
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_branch_sdk/flutter_branch_sdk.dart'; import 'package:flutter_branch_sdk/flutter_branch_sdk.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart'; import 'package:mypoint_flutter_app/core/utils/extensions/string_extension.dart';
import 'package:uni_links/uni_links.dart'; import 'package:uni_links/uni_links.dart';
import 'package:mypoint_flutter_app/directional/directional_screen.dart'; import 'package:mypoint_flutter_app/shared/navigation/directional_screen.dart';
import 'package:mypoint_flutter_app/extensions/crypto.dart' as mycrypto; import 'package:mypoint_flutter_app/core/utils/crypto.dart' as mycrypto;
import '../directional/directional_action_type.dart'; import '../../app/routing/directional_action_type.dart';
class DeepLinkService { class DeepLinkService {
DeepLinkService._internal(); DeepLinkService._internal();
...@@ -17,6 +17,7 @@ class DeepLinkService { ...@@ -17,6 +17,7 @@ class DeepLinkService {
bool _initialized = false; bool _initialized = false;
Future<void> initialize() async { Future<void> initialize() async {
if (kIsWeb) return;
if (_initialized) return; if (_initialized) return;
_initialized = true; _initialized = true;
debugPrint('🔗 Initializing DeepLinkService...'); debugPrint('🔗 Initializing DeepLinkService...');
...@@ -57,6 +58,10 @@ class DeepLinkService { ...@@ -57,6 +58,10 @@ class DeepLinkService {
} catch (_) {} } catch (_) {}
} }
bool handleIncomingUri(String uri) {
return _routeFromUriString(uri) ?? false;
}
void _listenLinkStream() { void _listenLinkStream() {
try { try {
_linkSub = linkStream.listen((uri) { _linkSub = linkStream.listen((uri) {
...@@ -67,10 +72,10 @@ class DeepLinkService { ...@@ -67,10 +72,10 @@ class DeepLinkService {
} }
// Firebase Dynamic Links removed due to version constraints. // Firebase Dynamic Links removed due to version constraints.
void _routeFromUriString(String uriStr) { bool? _routeFromUriString(String uriStr) {
debugPrint('🔗 Deep link received: $uriStr'); debugPrint('🔗 Deep link received: $uriStr');
final uri = Uri.tryParse(uriStr); final uri = Uri.tryParse(uriStr);
if (uri == null) return; if (uri == null) return false;
final type = uri.queryParameters[Defines.actionType] ?? uri.queryParameters['action_type']; final type = uri.queryParameters[Defines.actionType] ?? uri.queryParameters['action_type'];
final param = uri.queryParameters[Defines.actionParams] ?? uri.queryParameters['action_param']; final param = uri.queryParameters[Defines.actionParams] ?? uri.queryParameters['action_param'];
...@@ -91,13 +96,12 @@ class DeepLinkService { ...@@ -91,13 +96,12 @@ class DeepLinkService {
direction?.extraData = { direction?.extraData = {
'password': param, 'password': param,
}; };
direction?.begin(); return direction?.begin(); // Use if you need to attach to userInfo later
return; // Use if you need to attach to userInfo later
} }
} }
} }
final screen = DirectionalScreen.build(clickActionType: type, clickActionParam: param); final screen = DirectionalScreen.build(clickActionType: type, clickActionParam: param);
screen?.begin(); return screen?.begin();
} }
void _handleBranchSession(Map<dynamic, dynamic> data) { void _handleBranchSession(Map<dynamic, dynamic> data) {
......
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:mypoint_flutter_app/base/base_response_model.dart'; import 'package:mypoint_flutter_app/shared/widgets/base_view/base_response_model.dart';
import 'package:mypoint_flutter_app/configs/constants.dart'; import 'package:mypoint_flutter_app/app/config/constants.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart'; import 'package:mypoint_flutter_app/core/network/restful_api_client_all_request.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart'; import 'package:mypoint_flutter_app/shared/preferences/data_preference.dart';
import '../firebase/push_token_service.dart'; import '../firebase/push_token_service.dart';
import '../model/auth/login_token_response_model.dart'; import '../../features/login/model/login_token_response_model.dart';
import '../networking/restful_api_viewmodel.dart'; import '../network/restful_api_viewmodel.dart';
/// Login result enum để handle các trạng thái khác nhau /// Login result enum để handle các trạng thái khác nhau
enum LoginResult { enum LoginResult {
...@@ -43,15 +43,12 @@ class LoginService extends RestfulApiViewModel { ...@@ -43,15 +43,12 @@ class LoginService extends RestfulApiViewModel {
try { try {
// Step 1: Authenticate user // Step 1: Authenticate user
final authResponse = await client.login(phone, password); final authResponse = await client.login(phone, password);
if (!authResponse.isSuccess || authResponse.data == null) { if (!authResponse.isSuccess || authResponse.data == null) {
debugPrint('Login failed: ${authResponse.errorMessage}'); debugPrint('Login failed: ${authResponse.errorMessage}');
return _handleAuthError(authResponse); return _handleAuthError(authResponse);
} }
// Step 2: Save token // Step 2: Save token
await DataPreference.instance.saveLoginToken(authResponse.data!); await DataPreference.instance.saveLoginToken(authResponse.data!);
// Step 3: Get user profile (critical step) // Step 3: Get user profile (critical step)
final profileResult = await _getUserProfileWithRetry(); final profileResult = await _getUserProfileWithRetry();
if (profileResult.result != LoginResult.success) { if (profileResult.result != LoginResult.success) {
...@@ -62,16 +59,13 @@ class LoginService extends RestfulApiViewModel { ...@@ -62,16 +59,13 @@ class LoginService extends RestfulApiViewModel {
message: profileResult.message ?? Constants.commonError, message: profileResult.message ?? Constants.commonError,
); );
} }
// Step 4: Upload FCM token (non-critical, don't fail login if this fails) // Step 4: Upload FCM token (non-critical, don't fail login if this fails)
await _uploadFCMTokenSafely(); await _uploadFCMTokenSafely();
return LoginResponse(result: LoginResult.success); return LoginResponse(result: LoginResult.success);
} catch (e) { } catch (e) {
return LoginResponse( return LoginResponse(
result: LoginResult.networkError, result: LoginResult.networkError,
message: 'Đăng nhập thất bại: ${e.toString()}', message: ErrorCodes.commonError,
); );
} }
} }
...@@ -120,12 +114,10 @@ class LoginService extends RestfulApiViewModel { ...@@ -120,12 +114,10 @@ class LoginService extends RestfulApiViewModel {
for (int attempt = 1; attempt <= maxRetries; attempt++) { for (int attempt = 1; attempt <= maxRetries; attempt++) {
try { try {
final response = await client.getUserProfile(); final response = await client.getUserProfile();
if (response.isSuccess && response.data != null) { if (response.isSuccess && response.data != null) {
await DataPreference.instance.saveUserProfile(response.data!); await DataPreference.instance.saveUserProfile(response.data!);
return LoginResponse(result: LoginResult.success); return LoginResponse(result: LoginResult.success);
} }
// If not last attempt, wait before retry // If not last attempt, wait before retry
if (attempt < maxRetries) { if (attempt < maxRetries) {
await Future.delayed(Duration(seconds: attempt * 2)); await Future.delayed(Duration(seconds: attempt * 2));
...@@ -134,16 +126,16 @@ class LoginService extends RestfulApiViewModel { ...@@ -134,16 +126,16 @@ class LoginService extends RestfulApiViewModel {
if (attempt == maxRetries) { if (attempt == maxRetries) {
return LoginResponse( return LoginResponse(
result: LoginResult.networkError, result: LoginResult.networkError,
message: 'Không thể tải thông tin người dùng: ${e.toString()}', message: ErrorCodes.commonError,
); );
} }
await Future.delayed(Duration(seconds: attempt * 2)); await Future.delayed(Duration(seconds: attempt * 2));
} }
} }
debugPrint('Failed to fetch user profile after $maxRetries attempts');
return LoginResponse( return LoginResponse(
result: LoginResult.networkError, result: LoginResult.networkError,
message: 'Không thể tải thông tin người dùng sau $maxRetries lần thử', message: ErrorCodes.commonError,
); );
} }
...@@ -161,13 +153,10 @@ class LoginService extends RestfulApiViewModel { ...@@ -161,13 +153,10 @@ class LoginService extends RestfulApiViewModel {
Future<LoginResponse> biometricLogin(String phone) async { Future<LoginResponse> biometricLogin(String phone) async {
try { try {
final response = await client.loginWithBiometric(phone); final response = await client.loginWithBiometric(phone);
if (!response.isSuccess || response.data == null) { if (!response.isSuccess || response.data == null) {
return _handleAuthError(response); return _handleAuthError(response);
} }
await DataPreference.instance.saveLoginToken(response.data!); await DataPreference.instance.saveLoginToken(response.data!);
final profileResult = await _getUserProfileWithRetry(); final profileResult = await _getUserProfileWithRetry();
if (profileResult.result != LoginResult.success) { if (profileResult.result != LoginResult.success) {
await DataPreference.instance.clearLoginToken(); await DataPreference.instance.clearLoginToken();
...@@ -176,14 +165,12 @@ class LoginService extends RestfulApiViewModel { ...@@ -176,14 +165,12 @@ class LoginService extends RestfulApiViewModel {
message: profileResult.message ?? Constants.commonError, message: profileResult.message ?? Constants.commonError,
); );
} }
await _uploadFCMTokenSafely(); await _uploadFCMTokenSafely();
return LoginResponse(result: LoginResult.success); return LoginResponse(result: LoginResult.success);
} catch (e) { } catch (e) {
return LoginResponse( return LoginResponse(
result: LoginResult.networkError, result: LoginResult.networkError,
message: 'Đăng nhập sinh trắc thất bại: ${e.toString()}', message: ErrorCodes.commonError,
); );
} }
} }
......
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import '../network/dio_http_service.dart';
import '../networking/dio_http_service.dart'; import '../network/restful_api_client.dart';
import '../networking/restful_api_client.dart'; import '../network/restful_api_client_all_request.dart';
import '../networking/restful_api_client_all_request.dart'; import '../../shared/preferences/data_preference.dart';
import '../preference/data_preference.dart';
class LogoutService { class LogoutService {
LogoutService._(); LogoutService._();
......
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart'; import 'package:mypoint_flutter_app/core/utils/extensions/string_extension.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart'; import 'package:mypoint_flutter_app/core/network/restful_api_client_all_request.dart';
import '../configs/constants.dart'; import '../../app/config/constants.dart';
import '../model/auth/login_token_response_model.dart'; import '../../features/login/model/login_token_response_model.dart';
import '../networking/restful_api_viewmodel.dart'; import '../network/restful_api_viewmodel.dart';
import '../preference/data_preference.dart'; import '../../shared/preferences/data_preference.dart';
import '../base/app_navigator.dart'; import '../../app/routing/app_navigator.dart';
class TokenRefreshService extends RestfulApiViewModel { class TokenRefreshService extends RestfulApiViewModel {
static final TokenRefreshService _instance = TokenRefreshService._internal(); static final TokenRefreshService _instance = TokenRefreshService._internal();
......
...@@ -41,6 +41,10 @@ void webResetSDK() { ...@@ -41,6 +41,10 @@ void webResetSDK() {
// no-op on non-web // no-op on non-web
} }
void webNotifySplashScreenReady() {
// no-op on non-web
}
Future<dynamic> webConfigUIApp(Map<String, dynamic> config) async { Future<dynamic> webConfigUIApp(Map<String, dynamic> config) async {
return null; return null;
} }
......
// Web-specific implementations for x-app-sdk // Web-specific implementations for x-app-sdk
import 'dart:html' as html;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'x_app_sdk_service.dart'; import 'x_app_sdk_service.dart';
...@@ -81,6 +82,14 @@ void webResetSDK() { ...@@ -81,6 +82,14 @@ void webResetSDK() {
} }
} }
void webNotifySplashScreenReady() {
try {
html.window.dispatchEvent(html.CustomEvent('mypoint-splash-ready'));
} catch (e) {
debugPrint('❌ Error notifying splash readiness: $e');
}
}
Future<dynamic> webConfigUIApp(Map<String, dynamic> config) async { Future<dynamic> webConfigUIApp(Map<String, dynamic> config) async {
try { try {
return await XAppSDKService().configUIApp(config); return await XAppSDKService().configUIApp(config);
......
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