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

refactor logic

parent 89983084
...@@ -16,13 +16,17 @@ class AppNavigator { ...@@ -16,13 +16,17 @@ class AppNavigator {
static bool _authDialogShown = false; static bool _authDialogShown = false;
static bool _networkDialogShown = false; static bool _networkDialogShown = false;
static bool _errorDialogShown = false; static bool _errorDialogShown = false;
static bool _defaultDialogShown = false;
static bool get isShowingDialog =>
_authDialogShown || _networkDialogShown || _errorDialogShown || _defaultDialogShown;
static bool get isShowingDialog => _authDialogShown || _networkDialogShown || _errorDialogShown;
static BuildContext? get _ctx => key.currentContext; static BuildContext? get _ctx => key.currentContext;
static Future<void> showAuthAlertAndGoLogin(String message) async { static Future<void> showAuthAlertAndGoLogin(String message) async {
final description = 'Phiên đăng nhập của bạn đã hết hạn. Vui lòng đăng nhập lại để tiếp tục sử dụng ứng dụng.'; final description = 'Phiên đăng nhập của bạn đã hết hạn. Vui lòng đăng nhập lại để tiếp tục sử dụng ứng dụng.';
if (_authDialogShown || _ctx == null) return; if (_authDialogShown || _ctx == null) return;
if (Get.isDialogOpen ?? false) Get.back();
_authDialogShown = true; _authDialogShown = true;
final dataAlert = DataAlertModel( final dataAlert = DataAlertModel(
title: "Thông Báo", title: "Thông Báo",
...@@ -32,12 +36,12 @@ class AppNavigator { ...@@ -32,12 +36,12 @@ class AppNavigator {
AlertButton( AlertButton(
text: "Đã hiểu", text: "Đã hiểu",
onPressed: () { onPressed: () {
_authDialogShown = false;
if (kIsWeb) { if (kIsWeb) {
webCloseApp({ webCloseApp({
'message': message.isNotEmpty ? message : description, 'message': message.isNotEmpty ? message : description,
'timestamp': DateTime.now().millisecondsSinceEpoch, 'timestamp': DateTime.now().millisecondsSinceEpoch,
}); });
_authDialogShown = false;
return; return;
} }
final phone = DataPreference.instance.phoneNumberUsedForLoginScreen; final phone = DataPreference.instance.phoneNumberUsedForLoginScreen;
...@@ -47,14 +51,18 @@ class AppNavigator { ...@@ -47,14 +51,18 @@ class AppNavigator {
DataPreference.instance.clearData(); DataPreference.instance.clearData();
Get.offAllNamed(onboardingScreen); Get.offAllNamed(onboardingScreen);
} }
_authDialogShown = false;
}, },
bgColor: BaseColor.primary500, bgColor: BaseColor.primary500,
textColor: Colors.white, textColor: Colors.white,
), ),
], ],
); );
Get.dialog(CustomAlertDialog(alertData: dataAlert, showCloseButton: false), barrierDismissible: false); Get.dialog(CustomAlertDialog(alertData: dataAlert, showCloseButton: false), barrierDismissible: false).then((_) {
// Reset flag khi dialog đóng bằng barrierDismissible hoặc cách khác
if (_authDialogShown) {
_authDialogShown = false;
}
});
} }
static Future<void> showNoInternetAlert(String message, Callback retry, Callback close) async { static Future<void> showNoInternetAlert(String message, Callback retry, Callback close) async {
...@@ -90,7 +98,12 @@ class AppNavigator { ...@@ -90,7 +98,12 @@ class AppNavigator {
Get.dialog( Get.dialog(
CustomAlertDialog(alertData: dataAlert, showCloseButton: false, direction: ButtonsDirection.row), CustomAlertDialog(alertData: dataAlert, showCloseButton: false, direction: ButtonsDirection.row),
barrierDismissible: false, barrierDismissible: false,
); ).then((_) {
// Reset flag khi dialog đóng bằng barrierDismissible hoặc cách khác
if (_networkDialogShown) {
_networkDialogShown = false;
}
});
} }
static void showAlertError({ static void showAlertError({
...@@ -100,6 +113,7 @@ class AppNavigator { ...@@ -100,6 +113,7 @@ class AppNavigator {
bool showCloseButton = false, bool showCloseButton = false,
VoidCallback? onConfirmed, VoidCallback? onConfirmed,
}) { }) {
print("Show alert error: $_errorDialogShown");
if (_errorDialogShown || _ctx == null) return; if (_errorDialogShown || _ctx == null) return;
_errorDialogShown = true; _errorDialogShown = true;
Get.dialog( Get.dialog(
...@@ -126,18 +140,76 @@ class AppNavigator { ...@@ -126,18 +140,76 @@ class AppNavigator {
), ),
), ),
barrierDismissible: barrierDismissible ?? false, barrierDismissible: barrierDismissible ?? false,
); ).then((_) {
// Reset flag khi dialog đóng bằng barrierDismissible hoặc cách khác
if (_errorDialogShown) {
_errorDialogShown = false;
}
});
} }
static void showPopup({ static void showPopup({
required PopupDataModel data, required PopupDataModel data,
bool? barrierDismissibl, bool? barrierDismissible,
bool showCloseButton = false, bool showCloseButton = false,
ButtonsDirection direction = ButtonsDirection.column, ButtonsDirection direction = ButtonsDirection.column,
bool force = false,
}) { }) {
Get.dialog( showAlert(
CustomAlertDialog(alertData: data.dataAlertModel, showCloseButton: showCloseButton, direction: direction), data: data.dataAlertModel,
barrierDismissible: barrierDismissibl ?? true, barrierDismissible: barrierDismissible ?? true,
showCloseButton: showCloseButton,
direction: direction,
force: force,
); );
} }
static void showAlert({
required DataAlertModel data,
bool? barrierDismissible,
bool showCloseButton = true,
ButtonsDirection direction = ButtonsDirection.column,
bool force = false,
}) {
if (force) {
_defaultDialogShown = false;
if (Get.isDialogOpen ?? false) Get.back();
}
if (_defaultDialogShown || _ctx == null) return;
_defaultDialogShown = true;
// Wrap buttons với callback để reset flag
final wrappedData = DataAlertModel(
localHeaderImage: data.localHeaderImage,
urlHeaderImage: data.urlHeaderImage,
title: data.title,
description: data.description,
content: data.content,
buttons:
data.buttons?.map((button) {
if (button == null) return null;
return AlertButton(
text: button.text,
textColor: button.textColor,
bgColor: button.bgColor,
onPressed: () {
// Gọi callback gốc trước
button.onPressed();
// Sau đó reset flag
_defaultDialogShown = false;
},
);
}).toList(),
);
Get.dialog(
CustomAlertDialog(alertData: wrappedData, showCloseButton: showCloseButton, direction: direction),
barrierDismissible: barrierDismissible ?? false,
).then((_) {
// Reset flag khi dialog đóng bằng barrierDismissible hoặc cách khác
if (_defaultDialogShown) {
_defaultDialogShown = false;
}
});
}
} }
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:mypoint_flutter_app/base/app_navigator.dart';
import 'package:mypoint_flutter_app/networking/app_navigator.dart';
import 'package:mypoint_flutter_app/main.dart' show routeObserver; import 'package:mypoint_flutter_app/main.dart' show routeObserver;
import '../resources/base_color.dart';
import '../widgets/alert/custom_alert_dialog.dart'; import '../widgets/alert/custom_alert_dialog.dart';
import '../widgets/alert/data_alert_model.dart'; import '../widgets/alert/data_alert_model.dart';
import '../widgets/alert/popup_data_model.dart'; import '../widgets/alert/popup_data_model.dart';
...@@ -14,7 +12,6 @@ abstract class BaseScreen extends StatefulWidget { ...@@ -14,7 +12,6 @@ abstract class BaseScreen extends StatefulWidget {
abstract class BaseState<Screen extends BaseScreen> extends State<Screen> abstract class BaseState<Screen extends BaseScreen> extends State<Screen>
with WidgetsBindingObserver, RouteAware { with WidgetsBindingObserver, RouteAware {
bool _isVisible = false;
ModalRoute<dynamic>? _route; ModalRoute<dynamic>? _route;
@override @override
...@@ -125,13 +122,11 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen> ...@@ -125,13 +122,11 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen>
bool showCloseButton = false, bool showCloseButton = false,
ButtonsDirection direction = ButtonsDirection.column, ButtonsDirection direction = ButtonsDirection.column,
}) { }) {
Get.dialog( showAlert(
CustomAlertDialog( data: data.dataAlertModel,
alertData: data.dataAlertModel,
showCloseButton: showCloseButton,
direction: direction,
),
barrierDismissible: barrierDismissible ?? true, barrierDismissible: barrierDismissible ?? true,
showCloseButton: showCloseButton,
direction: direction,
); );
} }
...@@ -142,13 +137,11 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen> ...@@ -142,13 +137,11 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen>
bool showCloseButton = true, bool showCloseButton = true,
ButtonsDirection direction = ButtonsDirection.column, ButtonsDirection direction = ButtonsDirection.column,
}) { }) {
Get.dialog( AppNavigator.showAlert(
CustomAlertDialog( data: data,
alertData: data, barrierDismissible: barrierDismissible,
showCloseButton: showCloseButton, showCloseButton: showCloseButton,
direction: direction, direction: direction
),
barrierDismissible: barrierDismissible ?? false,
); );
} }
...@@ -160,28 +153,12 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen> ...@@ -160,28 +153,12 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen>
bool showCloseButton = true, bool showCloseButton = true,
VoidCallback? onConfirmed, VoidCallback? onConfirmed,
}) { }) {
if (AppNavigator.isShowingDialog) return; AppNavigator.showAlertError(
Get.dialog( content: content,
CustomAlertDialog( barrierDismissible: barrierDismissible,
showCloseButton: showCloseButton, headerImage: headerImage,
alertData: DataAlertModel( showCloseButton: showCloseButton,
localHeaderImage: headerImage, onConfirmed: onConfirmed,
title: "",
description: content,
buttons: [
AlertButton(
text: "Đã Hiểu",
onPressed: () {
Get.back();
onConfirmed?.call();
},
bgColor: BaseColor.primary500,
textColor: Colors.white,
),
],
),
),
barrierDismissible: barrierDismissible ?? false,
); );
} }
...@@ -193,7 +170,6 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen> ...@@ -193,7 +170,6 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen>
// MARK: - RouteAware Implementation // MARK: - RouteAware Implementation
@override @override
void didPush() { void didPush() {
_isVisible = true;
_handleRouteAppear(); _handleRouteAppear();
} }
......
...@@ -9,7 +9,7 @@ import 'package:mypoint_flutter_app/widgets/alert/popup_data_model.dart'; ...@@ -9,7 +9,7 @@ import 'package:mypoint_flutter_app/widgets/alert/popup_data_model.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import '../configs/constants.dart'; import '../configs/constants.dart';
import '../networking/app_navigator.dart'; import '../base/app_navigator.dart';
import '../networking/restful_api_viewmodel.dart'; import '../networking/restful_api_viewmodel.dart';
import '../screen/pageDetail/model/detail_page_rule_type.dart'; import '../screen/pageDetail/model/detail_page_rule_type.dart';
import '../screen/pipi/pipi_detail_screen.dart'; import '../screen/pipi/pipi_detail_screen.dart';
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/app_navigator.dart'; import 'package:mypoint_flutter_app/base/app_navigator.dart';
import 'package:mypoint_flutter_app/resources/base_color.dart'; import 'package:mypoint_flutter_app/resources/base_color.dart';
import 'package:mypoint_flutter_app/shared/router_gage.dart'; import 'package:mypoint_flutter_app/shared/router_gage.dart';
import 'package:mypoint_flutter_app/core/app_initializer.dart'; import 'package:mypoint_flutter_app/core/app_initializer.dart';
......
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import '../../configs/constants.dart'; import '../../configs/constants.dart';
import '../app_navigator.dart'; import '../../base/app_navigator.dart';
import '../dio_http_service.dart'; import '../dio_http_service.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart'; import 'package:mypoint_flutter_app/preference/data_preference.dart';
import '../../services/token_refresh_service.dart'; import '../../services/token_refresh_service.dart';
......
import 'dart:async'; import 'dart:async';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:mypoint_flutter_app/base/app_loading.dart'; import 'package:mypoint_flutter_app/base/app_loading.dart';
import '../app_navigator.dart'; import '../../base/app_navigator.dart';
import '../dio_http_service.dart'; import '../dio_http_service.dart';
import '../error_mapper.dart'; import '../error_mapper.dart';
......
...@@ -63,7 +63,6 @@ import '../screen/pageDetail/model/detail_page_rule_type.dart'; ...@@ -63,7 +63,6 @@ import '../screen/pageDetail/model/detail_page_rule_type.dart';
import '../screen/popup_manager/popup_manager_model.dart'; import '../screen/popup_manager/popup_manager_model.dart';
import '../screen/quiz_campaign/quiz_campaign_model.dart'; import '../screen/quiz_campaign/quiz_campaign_model.dart';
import '../screen/register_campaign/model/registration_form_package_model.dart'; import '../screen/register_campaign/model/registration_form_package_model.dart';
import '../screen/splash/splash_screen_viewmodel.dart';
import '../screen/topup/models/brand_network_model.dart'; import '../screen/topup/models/brand_network_model.dart';
import '../screen/traffic_service/traffic_service_model.dart'; import '../screen/traffic_service/traffic_service_model.dart';
import '../screen/transaction/history/transaction_category_model.dart'; import '../screen/transaction/history/transaction_category_model.dart';
......
...@@ -4,7 +4,7 @@ import 'package:mypoint_flutter_app/networking/restful_api_client.dart'; ...@@ -4,7 +4,7 @@ import 'package:mypoint_flutter_app/networking/restful_api_client.dart';
import '../base/base_response_model.dart'; import '../base/base_response_model.dart';
import '../base/base_view_model.dart'; import '../base/base_view_model.dart';
import '../configs/constants.dart'; import '../configs/constants.dart';
import 'app_navigator.dart'; import '../base/app_navigator.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'; import 'interceptor/network_error_gate.dart';
......
...@@ -42,7 +42,6 @@ class AffiliateTabViewModel extends RestfulApiViewModel { ...@@ -42,7 +42,6 @@ class AffiliateTabViewModel extends RestfulApiViewModel {
onFailure: (msg, _, __) async { onFailure: (msg, _, __) async {
affiliateBrands.clear(); affiliateBrands.clear();
}, },
showAppNavigatorDialog: true,
); );
} }
...@@ -65,7 +64,6 @@ class AffiliateTabViewModel extends RestfulApiViewModel { ...@@ -65,7 +64,6 @@ class AffiliateTabViewModel extends RestfulApiViewModel {
affiliateCategories.clear(); affiliateCategories.clear();
allAffiliateCategories.clear(); allAffiliateCategories.clear();
}, },
showAppNavigatorDialog: true,
); );
} }
...@@ -78,7 +76,6 @@ class AffiliateTabViewModel extends RestfulApiViewModel { ...@@ -78,7 +76,6 @@ class AffiliateTabViewModel extends RestfulApiViewModel {
onFailure: (msg, _, __) async { onFailure: (msg, _, __) async {
affiliateProducts.clear(); affiliateProducts.clear();
}, },
showAppNavigatorDialog: true,
); );
} }
...@@ -103,10 +100,6 @@ class AffiliateTabViewModel extends RestfulApiViewModel { ...@@ -103,10 +100,6 @@ class AffiliateTabViewModel extends RestfulApiViewModel {
onShowAffiliateBrandPopup?.call((data, category.name ?? '')); onShowAffiliateBrandPopup?.call((data, category.name ?? ''));
} }
}, },
onFailure: (msg, _, __) async {
// Không cần làm gì, error đã được handle bởi callApi
},
showAppNavigatorDialog: true,
); );
} }
} }
\ No newline at end of file
...@@ -5,36 +5,28 @@ import '../../networking/restful_api_viewmodel.dart'; ...@@ -5,36 +5,28 @@ import '../../networking/restful_api_viewmodel.dart';
import 'models/affiliate_brand_detail_model.dart'; import 'models/affiliate_brand_detail_model.dart';
class AffiliateBrandDetailViewModel extends RestfulApiViewModel { class AffiliateBrandDetailViewModel extends RestfulApiViewModel {
String brandId; final String brandId;
AffiliateBrandDetailViewModel(this.brandId); AffiliateBrandDetailViewModel(this.brandId);
void Function(String message)? onShowAlertError; void Function(String message)? onShowAlertError;
var isLoading = false.obs; final Rxn<AffiliateBrandDetailModel> brandDetailData = Rxn<AffiliateBrandDetailModel>();
var brandDetailData = Rxn<AffiliateBrandDetailModel>();
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
_getAffiliateBrandDetail(); _fetchDetail();
} }
Future<void> _getAffiliateBrandDetail() async { Future<void> _fetchDetail() async {
showLoading(); await callApi<AffiliateBrandDetailModel>(
if (isLoading.value) return; request: () => client.getAffiliateBrandDetail(brandId),
try { onSuccess: (data, _) {
isLoading.value = true; brandDetailData.value = data;
final response = await client.getAffiliateBrandDetail(brandId); },
hideLoading(); onFailure: (msg, _, _) async {
if (response.isSuccess) { brandDetailData.value = null;
brandDetailData.value = response.data; onShowAlertError?.call(msg.isNotEmpty ? msg : Constants.commonError);
} else { },
onShowAlertError?.call(response.errorMessage ?? Constants.commonError); );
}
} catch (error) {
showLoading();
onShowAlertError?.call("Error fetching product detail: $error");
} finally {
isLoading.value = false;
}
} }
} }
import 'package:get/get_rx/src/rx_types/rx_types.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/configs/constants.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart'; import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
import '../../networking/restful_api_viewmodel.dart'; import '../../networking/restful_api_viewmodel.dart';
import '../affiliate/model/affiliate_brand_model.dart'; import '../affiliate/model/affiliate_brand_model.dart';
import '../affiliate/model/affiliate_category_model.dart'; import '../affiliate/model/affiliate_category_model.dart';
class AffiliateCategoryGridViewModel extends RestfulApiViewModel { class AffiliateCategoryGridViewModel extends RestfulApiViewModel {
final RxBool isLoading = false.obs;
void Function((List<AffiliateBrandModel>, String) data)? onShowAffiliateBrandPopup; void Function((List<AffiliateBrandModel>, String) data)? onShowAffiliateBrandPopup;
void Function(String message)? onShowAlertError;
affiliateBrandGetListBuyCategory(AffiliateCategoryModel category) async { Future<void> affiliateBrandGetListBuyCategory(AffiliateCategoryModel category) async {
showLoading(); await callApi<List<AffiliateBrandModel>>(
try { request: () => client.affiliateBrandGetList(
final result = await client.affiliateBrandGetList(categoryCode: AffiliateCategoryModel.codeToJson(category.code)); categoryCode: AffiliateCategoryModel.codeToJson(category.code),
hideLoading(); ),
final data = result.data ?? []; onSuccess: (data, _) {
if (result.isSuccess && data.isNotEmpty) { if (data.isNotEmpty) {
onShowAffiliateBrandPopup?.call((data, category.name ?? '')); onShowAffiliateBrandPopup?.call((data, category.name ?? ''));
} }
} catch (error) { },
hideLoading(); onFailure: (msg, _, _) async {
print("Error fetching affiliate brands: $error"); onShowAlertError?.call(msg);
} },
);
} }
} }
\ No newline at end of file
...@@ -108,7 +108,7 @@ class _BankAccountDetailScreenState extends BaseState<BankAccountDetailScreen> w ...@@ -108,7 +108,7 @@ class _BankAccountDetailScreenState extends BaseState<BankAccountDetailScreen> w
); );
} }
_showAlertConfirmLogout() { void _showAlertConfirmLogout() {
final dataAlert = DataAlertModel( final dataAlert = DataAlertModel(
title: "Xoá thẻ?", title: "Xoá thẻ?",
description: "Bạn có chắc muốn xoá thẻ/tài khoản này?", description: "Bạn có chắc muốn xoá thẻ/tài khoản này?",
......
...@@ -14,41 +14,31 @@ class BankAccountDetailViewModel extends RestfulApiViewModel { ...@@ -14,41 +14,31 @@ class BankAccountDetailViewModel extends RestfulApiViewModel {
BankAccountDetailViewModel({required this.model}); BankAccountDetailViewModel({required this.model});
changeDefaultBankAccount() async { Future<void> changeDefaultBankAccount() async {
final revertDefault = !isDefault.value; final revertDefault = !isDefault.value;
final accountId = model.id.toString(); final accountId = model.id.toString();
showLoading(); await callApi<String?>(
try { request: () => client.setDefaultBankAccount(accountId, revertDefault),
final response = await client.setDefaultBankAccount(accountId, revertDefault); onSuccess: (data, _) {
hideLoading();
if (response.isSuccess) {
isDefault.value = revertDefault; isDefault.value = revertDefault;
showToastMessage(response.data ?? response.message ?? "Cập nhật thành công"); showToastMessage(data ?? "Cập nhật thành công");
} else { },
onShowAlertError?.call(response.message ?? Constants.commonError); onFailure: (msg, _, _) async {
} onShowAlertError?.call(msg.isNotEmpty ? msg : Constants.commonError);
} catch (error) { },
hideLoading(); );
onShowAlertError?.call(Constants.commonError);
}
} }
deleteBankAccount() async { Future<void> deleteBankAccount() async {
final accountId = model.id.toString(); final accountId = model.id.toString();
showLoading(); await callApi<String?>(
try { request: () => client.deleteBankAccount(accountId),
final response = await client.deleteBankAccount(accountId); onSuccess: (data, _) {
hideLoading(); deleteBackAccountSuccess?.call(data ?? "Xoá tài khoản thành công");
if (response.isSuccess) { },
deleteBackAccountSuccess?.call(response.data ?? response.message ?? "Xoá tài khoản thành công"); onFailure: (msg, _, _) async {
} else { onShowAlertError?.call(msg.isNotEmpty ? msg : Constants.commonError);
onShowAlertError?.call(response.message ?? Constants.commonError); },
} );
} catch (error) {
hideLoading();
onShowAlertError?.call(Constants.commonError);
} finally {
hideLoading();
}
} }
} }
\ No newline at end of file
import 'package:flutter/foundation.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart'; import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
import '../../networking/restful_api_viewmodel.dart'; import '../../networking/restful_api_viewmodel.dart';
...@@ -13,16 +12,15 @@ class BankAccountManagerViewModel extends RestfulApiViewModel { ...@@ -13,16 +12,15 @@ class BankAccountManagerViewModel extends RestfulApiViewModel {
getBankAccountList(); getBankAccountList();
} }
getBankAccountList() async { Future<void> getBankAccountList() async {
showLoading(); await callApi<List<BankAccountInfoModel>>(
try { request: () => client.getOrderPaymentMyAccounts(),
final result = await client.getOrderPaymentMyAccounts(); onSuccess: (data, _) {
hideLoading(); bankAccounts.assignAll(data);
bankAccounts.value = result.data ?? []; },
} catch (error) { onFailure: (_, _, _) async {
hideLoading(); bankAccounts.clear();
} finally { },
hideLoading(); );
}
} }
} }
\ No newline at end of file
...@@ -43,10 +43,14 @@ class _Campaign7DayScreenState extends BaseState<Campaign7DayScreen> with BasicS ...@@ -43,10 +43,14 @@ class _Campaign7DayScreenState extends BaseState<Campaign7DayScreen> with BasicS
_viewModel = Get.put(Campaign7DayViewModel(campaignId: campaignId)); _viewModel = Get.put(Campaign7DayViewModel(campaignId: campaignId));
_viewModel.getLiveTransactions(); _viewModel.getLiveTransactions();
_viewModel.getCampaign7DayInfo(); _viewModel.getCampaign7DayInfo();
_viewModel.onShowAlertError = (message) { _viewModel.onShowAlertError = (message, onBack) {
if (message.isNotEmpty) { if (message.isEmpty) return;
showAlertError(content: message); showAlertError(
} content: message,
showCloseButton: !onBack,
onConfirmed: () {
if (onBack) Get.back();
});
}; };
_viewModel.submitPerformMissionResponse = (mission) { _viewModel.submitPerformMissionResponse = (mission) {
final popup = mission.popup; final popup = mission.popup;
......
...@@ -7,63 +7,69 @@ import 'models/campaign_7day_mission_model.dart'; ...@@ -7,63 +7,69 @@ import 'models/campaign_7day_mission_model.dart';
import 'models/campaign_7day_reward_model.dart'; import 'models/campaign_7day_reward_model.dart';
class Campaign7DayViewModel extends RestfulApiViewModel { class Campaign7DayViewModel extends RestfulApiViewModel {
String campaignId; final String campaignId;
var liveTransactions = RxList<String>(); final RxList<String> liveTransactions = <String>[].obs;
var campaign7DayInfo = Rxn<Campaign7DayInfoModel>(); final Rxn<Campaign7DayInfoModel> campaign7DayInfo = Rxn<Campaign7DayInfoModel>();
void Function(String message)? onShowAlertError; void Function(String message, bool onBack)? onShowAlertError;
void Function(Campaign7DayMissionModel mission)? submitPerformMissionResponse; void Function(Campaign7DayMissionModel mission)? submitPerformMissionResponse;
void Function(List<Campaign7DayRewardModel> rewards)? getCampaignRewardsResponse; void Function(List<Campaign7DayRewardModel> rewards)? getCampaignRewardsResponse;
Campaign7DayViewModel({required this.campaignId}); Campaign7DayViewModel({required this.campaignId});
void getLiveTransactions() { void getLiveTransactions() {
client.getCampaignLiveTransactions(campaignId).then((value) { callApi<List<String>>(
liveTransactions.value = value.data ?? []; request: () => client.getCampaignLiveTransactions(campaignId),
}); onSuccess: (data, _) {
liveTransactions.assignAll(data);
},
withLoading: false,
);
} }
void getCampaignRewards() { void getCampaignRewards() {
showLoading(); callApi<List<Campaign7DayRewardModel>>(
client.getCampaignRewards(campaignId).then((value) { request: () => client.getCampaignRewards(campaignId),
hideLoading(); onSuccess: (data, _) {
final data = value.data ?? []; if (data.isEmpty) {
if (!value.isSuccess) { onShowAlertError?.call("Bạn chưa có phần thưởng nào. Vui lòng hoàn thành các nhiệm vụ để nhận thưởng!", false);
onShowAlertError?.call(value.errorMessage ?? Constants.commonError); } else {
return; getCampaignRewardsResponse?.call(data);
} }
if (data.isEmpty) { },
onShowAlertError?.call("Bạn chưa có phần thưởng nào. Vui lòng hoàn thành các nhiệm vụ để nhận thưởng!"); onFailure: (msg, _, _) async {
} else { onShowAlertError?.call(msg.isNotEmpty ? msg : Constants.commonError, false);
getCampaignRewardsResponse?.call(data); },
} );
});
} }
void getCampaign7DayInfo({bool silent = false}) { void getCampaign7DayInfo({bool silent = false}) {
client.getCampaignMissions(campaignId).then((value) { callApi<Campaign7DayInfoModel>(
if (!value.isSuccess && !silent) { request: () => client.getCampaignMissions(campaignId),
onShowAlertError?.call(value.errorMessage ?? Constants.commonError); onSuccess: (data, _) {
} campaign7DayInfo.value = data;
campaign7DayInfo.value = value.data; },
}); onFailure: (msg, _, _) async {
if (!silent) onShowAlertError?.call(msg.isNotEmpty ? msg : Constants.commonError, true);
},
);
} }
void submitPerformMission(Campaign7DayMissionModel mission) { void submitPerformMission(Campaign7DayMissionModel mission) {
if (!mission.isReady) return; if (!mission.isReady) return;
showLoading(); callApi<void>(
client.submitPerformMission(mission, campaignId).then((value) { request: () => client.submitPerformMission(mission, campaignId),
hideLoading(); onSuccess: (_, __) {
if (value.isSuccess) {
getCampaign7DayInfo(silent: true); getCampaign7DayInfo(silent: true);
if (mission.popup != null) { if (mission.popup != null) {
submitPerformMissionResponse?.call(mission); submitPerformMissionResponse?.call(mission);
} else { } else {
mission.directionScreen?.begin(); mission.directionScreen?.begin();
} }
} else { },
onShowAlertError?.call(value.errorMessage ?? Constants.commonError); onFailure: (msg, _, _) async {
} onShowAlertError?.call(msg.isNotEmpty ? msg : Constants.commonError, false);
}); },
);
} }
} }
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart'; import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
import '../../base/base_response_model.dart';
import '../../networking/restful_api_viewmodel.dart'; import '../../networking/restful_api_viewmodel.dart';
import '../../configs/constants.dart'; import '../../configs/constants.dart';
import '../../preference/data_preference.dart'; import '../../preference/data_preference.dart';
...@@ -7,12 +8,13 @@ import '../create_pass/change_pass_repository.dart'; ...@@ -7,12 +8,13 @@ import '../create_pass/change_pass_repository.dart';
import '../create_pass/create_pass_screen.dart'; import '../create_pass/create_pass_screen.dart';
import '../login/login_viewmodel.dart'; import '../login/login_viewmodel.dart';
import '../otp/forgot_pass_otp_repository.dart'; import '../otp/forgot_pass_otp_repository.dart';
import '../otp/model/create_otp_response_model.dart';
import '../otp/otp_screen.dart'; import '../otp/otp_screen.dart';
class ChangePassViewModel extends RestfulApiViewModel { class ChangePassViewModel extends RestfulApiViewModel {
var isPasswordVisible = false.obs; final RxBool isPasswordVisible = false.obs;
var password = "".obs; final RxString password = "".obs;
var loginState = LoginState.idle.obs; final Rx<LoginState> loginState = LoginState.idle.obs;
void Function(String message)? onShowAlertError; void Function(String message)? onShowAlertError;
void onPasswordChanged(String value) { void onPasswordChanged(String value) {
...@@ -28,27 +30,29 @@ class ChangePassViewModel extends RestfulApiViewModel { ...@@ -28,27 +30,29 @@ class ChangePassViewModel extends RestfulApiViewModel {
isPasswordVisible.value = !isPasswordVisible.value; isPasswordVisible.value = !isPasswordVisible.value;
} }
void onForgotPassPressed(String phone) { // void onForgotPassPressed(String phone) {
showLoading(); // callApi<CreateOTPResponseModel>(
client.otpCreateNew(phone).then((value) { // request: () => client.otpCreateNew(phone),
hideLoading(); // onSuccess: (data, _) {
// TODO: handle error later // final ttl = data.resendAfterSecond ?? Constants.otpTtl;
if (value.isSuccess) { // Get.to(OtpScreen(repository: ForgotPassOTPRepository(phone, ttl)));
Get.to(OtpScreen(repository: ForgotPassOTPRepository(phone, value.data?.resendAfterSecond ?? Constants.otpTtl))); // },
} // onFailure: (msg, _, _) async {
}); // onShowAlertError?.call(msg.isNotEmpty ? msg : Constants.commonError);
} // },
// );
// }
Future<void> accountCheckForPasswordChange() async { Future<void> accountCheckForPasswordChange() async {
showLoading(); final phone = DataPreference.instance.phone ?? "";
final phone = await DataPreference.instance.phone ?? ""; await callApi<EmptyCodable>(
client.accountLoginForPasswordChange(phone, password.value).then((value) { request: () => client.accountLoginForPasswordChange(phone, password.value),
hideLoading(); onSuccess: (_, _) {
if (value.isSuccess) {
Get.to(CreatePasswordScreen(repository: ChangePasswordRepository(phone))); Get.to(CreatePasswordScreen(repository: ChangePasswordRepository(phone)));
} else { },
onShowAlertError?.call(value.errorMessage ?? Constants.commonError); onFailure: (msg, _, _) async {
} onShowAlertError?.call(msg.isNotEmpty ? msg : Constants.commonError);
}); },
);
} }
} }
// import 'dart:math';
// import 'package:fl_chart/fl_chart.dart';
// import 'package:flutter/material.dart';
//
// /// Dữ liệu 30 ngày, mỗi phần tử là kWh (double).
// class EnergyMonthBarChart extends StatefulWidget {
// final List<double> values; // length 28-31 (tháng)
// final DateTime startDate; // ngày đầu (ví dụ: 1/9/2025)
// final Color barColor;
// final String unit; // "kWh"
// final double? maxY; // nếu null sẽ auto
// final bool showGrid;
//
// const EnergyMonthBarChart({
// super.key,
// required this.values,
// required this.startDate,
// this.barColor = const Color(0xFF3B5AFB),
// this.unit = 'kWh',
// this.maxY,
// this.showGrid = true,
// });
//
// @override
// State<EnergyMonthBarChart> createState() => _EnergyMonthBarChartState();
// }
//
// class _EnergyMonthBarChartState extends State<EnergyMonthBarChart> {
// int? _touchedIndex;
//
// int get length => widget.values.length;
// double get _computedMaxY {
// final m = widget.values.fold<double>(0, (p, v) => max(p, v));
// if (m == 0) return 10;
// final step = 4; // nấc hiển thị
// return (m / step).ceil() * step.toDouble() + 2; // dư chút cho đẹp
// }
//
// // Ngày hiện tại nằm trong dải?
// int? get _todayIndex {
// final today = DateTime.now();
// final s = DateUtils.dateOnly(widget.startDate);
// for (int i = 0; i < length; i++) {
// final d = DateUtils.addDaysToDate(s, i);
// if (DateUtils.isSameDay(d, today)) return i;
// }
// return null;
// }
//
// String _weekdayLabel(DateTime d) {
// const labels = ['CN','T2','T3','T4','T5','T6','T7'];
// return labels[d.weekday % 7];
// }
//
// @override
// Widget build(BuildContext context) {
// final theme = Theme.of(context);
// final maxY = widget.maxY ?? _computedMaxY;
// final sDate = DateUtils.dateOnly(widget.startDate);
//
// // barWidth để fit 30 cột gọn trong thẻ:
// final barWidth = 12.0;
// final groups = List.generate(length, (i) {
// final v = widget.values[i];
// return BarChartGroupData(
// x: i,
// barRods: [
// BarChartRodData(
// toY: v,
// width: barWidth,
// borderRadius: const BorderRadius.vertical(top: Radius.circular(8)),
// gradient: LinearGradient(
// begin: Alignment.bottomCenter,
// end: Alignment.topCenter,
// colors: [
// widget.barColor.withOpacity(0.25),
// widget.barColor,
// ],
// ),
// ),
// ],
// );
// });
//
// // trục dưới: hiển thị thứ & ngày (thưa để đỡ rối)
// Widget bottomTitles(double value, TitleMeta meta) {
// final i = value.toInt();
// if (i < 0 || i >= length) return const SizedBox.shrink();
// final d = DateUtils.addDaysToDate(sDate, i);
// // hiển thị cách 2 ngày 1 lần cho gọn
// if (i % 2 != 0) return const SizedBox.shrink();
// final isToday = _todayIndex == i;
// return Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// Text(
// _weekdayLabel(d),
// style: theme.textTheme.labelSmall?.copyWith(
// color: isToday ? widget.barColor : Colors.black54,
// fontWeight: isToday ? FontWeight.w600 : FontWeight.w400,
// ),
// ),
// const SizedBox(height: 2),
// Text(
// '${d.day}',
// style: theme.textTheme.labelSmall?.copyWith(
// color: isToday ? widget.barColor : Colors.black54,
// fontWeight: isToday ? FontWeight.w600 : FontWeight.w400,
// ),
// ),
// ],
// );
// }
//
// // trục trái: 0,4,8,...
// Widget leftTitles(double value, TitleMeta meta) {
// if (value % 4 != 0) return const SizedBox.shrink();
// return Text(
// value.toInt().toString(),
// style: theme.textTheme.labelSmall?.copyWith(color: Colors.black45),
// );
// }
//
// final chart = BarChart(
// BarChartData(
// maxY: maxY,
// minY: 0,
// barGroups: groups,
// gridData: FlGridData(
// show: widget.showGrid,
// horizontalInterval: 4,
// getDrawingHorizontalLine: (v) => FlLine(
// color: Colors.black12,
// strokeWidth: 1,
// dashArray: [4, 4],
// ),
// drawVerticalLine: false,
// ),
// borderData: FlBorderData(show: false),
// titlesData: FlTitlesData(
// leftTitles: AxisTitles(
// sideTitles: SideTitles(showTitles: true, reservedSize: 28, getTitlesWidget: leftTitles),
// ),
// rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
// topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
// bottomTitles: AxisTitles(
// sideTitles: SideTitles(
// showTitles: true,
// reservedSize: 34,
// getTitlesWidget: bottomTitles,
// ),
// ),
// ),
// barTouchData: BarTouchData(
// enabled: true,
// handleBuiltInTouches: false, // tự custom tooltip
// touchCallback: (evt, resp) {
// setState(() {
// _touchedIndex = resp?.spot?.touchedBarGroupIndex;
// });
// if (resp?.spot != null && evt.isInterestedForInteractions) {
// final i = resp!.spot!.touchedBarGroupIndex;
// final v = widget.values[i];
// final d = DateUtils.addDaysToDate(sDate, i);
// final text = 'Số điện: ${v.toStringAsFixed(v % 1 == 0 ? 0 : 1)} ${widget.unit}';
// final overlay = Overlay.of(context);
// final entry = OverlayEntry(
// builder: (_) => _TooltipBubble(
// text: text,
// anchor: evt.localPosition,
// ),
// );
// overlay.insert(entry);
// Future.delayed(const Duration(milliseconds: 900), entry.remove);
// }
// },
// ),
// ),
// swapAnimationDuration: const Duration(milliseconds: 300),
// );
//
// // Vạch dọc nét đứt tại ngày hiện tại (nếu nằm trong dải)
// final todayIndex = _todayIndex;
//
// return Container(
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(24),
// boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 12, offset: Offset(0, 4))],
// ),
// padding: const EdgeInsets.fromLTRB(16, 16, 16, 12),
// child: SizedBox(
// height: 260,
// child: LayoutBuilder(
// builder: (ctx, cons) {
// final chartWidget = Padding(
// padding: const EdgeInsets.only(right: 8),
// child: chart,
// );
//
// if (todayIndex == null) return chartWidget;
//
// // Tính vị trí X ước lượng của cột today để vẽ vạch (theo tổng chiều rộng)
// // fl_chart không expose trực tiếp nên ta ước lượng theo spacing mặc định:
// final groupSpace = 8.0;
// final totalW = length * barWidth + (length - 1) * groupSpace;
// final usableW = cons.maxWidth - 28 /*left titles approx*/ - 8 /*right pad*/;
// final scale = usableW / totalW;
// final x = 28 + (todayIndex * (barWidth + groupSpace) + barWidth / 2) * scale;
//
// return Stack(
// children: [
// chartWidget,
// // vạch dọc nét đứt
// Positioned.fill(
// child: IgnorePointer(
// child: CustomPaint(
// painter: _DashedVerticalLinePainter(
// x: x,
// color: widget.barColor.withOpacity(0.5),
// ),
// ),
// ),
// ),
// ],
// );
// },
// ),
// ),
// );
// }
// }
//
// /// Tooltip đơn giản đặt gần vị trí chạm
// class _TooltipBubble extends StatelessWidget {
// final String text;
// final Offset anchor;
// const _TooltipBubble({required this.text, required this.anchor});
//
// @override
// Widget build(BuildContext context) {
// final theme = Theme.of(context);
// return Positioned(
// left: anchor.dx + 8,
// top: max(8, anchor.dy - 36),
// child: Material(
// color: Colors.transparent,
// child: Container(
// padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(10),
// boxShadow: const [BoxShadow(color: Colors.black26, blurRadius: 8)],
// ),
// child: Text(
// text,
// style: theme.textTheme.labelMedium?.copyWith(fontWeight: FontWeight.w600),
// ),
// ),
// ),
// );
// }
// }
//
// class _DashedVerticalLinePainter extends CustomPainter {
// final double x;
// final Color color;
// const _DashedVerticalLinePainter({required this.x, required this.color});
// @override
// void paint(Canvas canvas, Size size) {
// final paint = Paint()
// ..color = color
// ..strokeWidth = 2;
// const dash = 6.0;
// const gap = 6.0;
// double y = 0;
// while (y < size.height) {
// canvas.drawLine(Offset(x, y), Offset(x, min(y + dash, size.height)), paint);
// y += dash + gap;
// }
// }
// @override
// bool shouldRepaint(covariant _DashedVerticalLinePainter old) => old.x != x || old.color != color;
// }
...@@ -34,7 +34,7 @@ class _ContactsListScreenState extends State<ContactsListScreen> { ...@@ -34,7 +34,7 @@ class _ContactsListScreenState extends State<ContactsListScreen> {
}; };
} }
_openSMS(String sms, String phone) async { Future<void> _openSMS(String sms, String phone) async {
final uri = Uri(scheme: 'sms', path: phone, queryParameters: <String, String>{'body': sms}); final uri = Uri(scheme: 'sms', path: phone, queryParameters: <String, String>{'body': sms});
if (await canLaunchUrl(uri)) { if (await canLaunchUrl(uri)) {
...@@ -42,7 +42,7 @@ class _ContactsListScreenState extends State<ContactsListScreen> { ...@@ -42,7 +42,7 @@ class _ContactsListScreenState extends State<ContactsListScreen> {
} }
} }
_onSearchChanged(String query) { void _onSearchChanged(String query) {
setState(() { setState(() {
displayContacts = searchContacts(query); displayContacts = searchContacts(query);
}); });
......
...@@ -93,7 +93,7 @@ class _DataNetworkServiceScreenState extends BaseState<DataNetworkServiceScreen> ...@@ -93,7 +93,7 @@ class _DataNetworkServiceScreenState extends BaseState<DataNetworkServiceScreen>
}); });
} }
_redeemProductMobileCard() { void _redeemProductMobileCard() {
print("redeem ${UserPointManager().point} >= ${_viewModel.payPoint}"); print("redeem ${UserPointManager().point} >= ${_viewModel.payPoint}");
final isValidInput = final isValidInput =
(_viewModel.phoneNumber.value.trim().length >= 10) && (_viewModel.selectedProduct.value != null); (_viewModel.phoneNumber.value.trim().length >= 10) && (_viewModel.selectedProduct.value != null);
...@@ -108,7 +108,7 @@ class _DataNetworkServiceScreenState extends BaseState<DataNetworkServiceScreen> ...@@ -108,7 +108,7 @@ class _DataNetworkServiceScreenState extends BaseState<DataNetworkServiceScreen>
_showAlertConfirmRedeemProduct(); _showAlertConfirmRedeemProduct();
} }
_showAlertConfirmRedeemProduct() { void _showAlertConfirmRedeemProduct() {
final dataAlert = DataAlertModel( final dataAlert = DataAlertModel(
title: "Xác nhận", title: "Xác nhận",
description: "Bạn có muốn sử dụng ${_viewModel.payPoint.money(CurrencyUnit.point)} MyPoint để đổi gói cước này không?", description: "Bạn có muốn sử dụng ${_viewModel.payPoint.money(CurrencyUnit.point)} MyPoint để đổi gói cước này không?",
......
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