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

refactor logic

parent 89983084
......@@ -3,13 +3,14 @@ import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import 'package:mypoint_flutter_app/screen/data_network_service/product_network_data_model.dart';
import 'package:mypoint_flutter_app/widgets/custom_toast_message.dart';
import '../../base/base_response_model.dart';
import '../../networking/restful_api_viewmodel.dart';
import '../../configs/constants.dart';
import '../../preference/contact_storage_service.dart';
import '../../preference/point/point_manager.dart';
import '../topup/models/brand_network_model.dart';
import '../voucher/models/product_brand_model.dart';
import '../voucher/models/product_model.dart';
import '../voucher/models/product_type.dart';
class DataNetworkServiceViewModel extends RestfulApiViewModel {
final RxList<String> histories = <String>[].obs;
......@@ -17,9 +18,9 @@ class DataNetworkServiceViewModel extends RestfulApiViewModel {
final RxList<TopUpNetworkDataModel> topUpNetworkData = <TopUpNetworkDataModel>[].obs;
final Map<String, List<TopUpNetworkDataModel>> _allValue = {};
var selectedBrand = Rxn<ProductBrandModel>();
var selectedProduct = Rxn<ProductNetworkDataModel>();
var phoneNumber = ''.obs;
final Rxn<ProductBrandModel> selectedBrand = Rxn<ProductBrandModel>();
final Rxn<ProductNetworkDataModel> selectedProduct = Rxn<ProductNetworkDataModel>();
final RxString phoneNumber = ''.obs;
void Function(String message)? onShowAlertError;
void Function(String message)? onShowAlertRedeemSuccess;
......@@ -48,43 +49,44 @@ class DataNetworkServiceViewModel extends RestfulApiViewModel {
}
}
firstLoadNetworkData() async {
_getNetworkBrands();
Future<void> firstLoadNetworkData() async {
await _getNetworkBrands();
}
_getNetworkBrands() {
showLoading();
client.productTopUpBrands().then((response) {
topUpBrands.assignAll(response.data ?? []);
hideLoading();
checkMobileNetwork();
}).catchError((error) {
hideLoading();
print('Error fetching brands topup: $error');
});
Future<void> _getNetworkBrands() async {
await callApi<List<ProductBrandModel>>(
request: () => client.productTopUpBrands(),
onSuccess: (data, _) {
topUpBrands.assignAll(data);
checkMobileNetwork();
},
onFailure: (mgs, _, _) async {
topUpBrands.clear();
onShowAlertError?.call(mgs);
},
);
}
checkMobileNetwork() {
showLoading();
client.checkMobileNetwork(phoneNumber.value).then((response) {
final brandCode = response.data?.brand ?? '';
final brand = topUpBrands.isNotEmpty
? topUpBrands.firstWhere(
(brand) => brand.code == brandCode,
orElse: () => topUpBrands.first,
) : null;
selectedBrand.value = brand;
hideLoading();
getTelcoDetail();
}).catchError((error) {
final first = topUpBrands.firstOrNull;
if (first != null) {
selectedBrand.value = first;
}
hideLoading();
getTelcoDetail();
print('Error checking mobile network: $error');
});
Future<void> checkMobileNetwork() async {
await callApi<BrandNameCheckResponse>(
request: () => client.checkMobileNetwork(phoneNumber.value),
onSuccess: (data, _) {
final brandCode = data.brand ?? '';
var brand = topUpBrands.isNotEmpty
? topUpBrands.firstWhere(
(b) => b.code == brandCode,
orElse: () => topUpBrands.first,
)
: topUpBrands.firstOrNull;
selectedBrand.value = brand;
getTelcoDetail();
},
onFailure: (_, _, _) async {
final first = topUpBrands.firstOrNull;
if (first != null) selectedBrand.value = first;
getTelcoDetail();
},
);
}
Future<void> getTelcoDetail({String? selected}) async {
......@@ -94,12 +96,10 @@ class DataNetworkServiceViewModel extends RestfulApiViewModel {
void makeSelected(List<TopUpNetworkDataModel> data) {
bool didSelect = false;
final list = data
.expand((e) => e.products ?? [])
.toList();
final list = data.expand((e) => e.products ?? []).toList();
if (selected != null && selected.isNotEmpty) {
for (var item in list) {
final isMatch = item == int.tryParse(selected);
final isMatch = item.id == int.tryParse(selected);
if (isMatch) {
selectedProduct.value = item;
didSelect = true;
......@@ -117,43 +117,34 @@ class DataNetworkServiceViewModel extends RestfulApiViewModel {
makeSelected(cached);
return;
}
showLoading();
try {
final result = await client.getNetworkProducts((id ?? 0).toString());
var data = result.data ?? [];
data = data
.where((e) => e.products?.isNotEmpty == true)
.toList();
_allValue[code ?? ""] = data;
topUpNetworkData.assignAll(data);
makeSelected(data);
hideLoading();
} catch (error) {
print("Error fetching all products: $error");
hideLoading();
}
await callApi<List<TopUpNetworkDataModel>>(
request: () => client.getNetworkProducts((id ?? 0).toString()),
onSuccess: (data, _) {
final filtered = (data)
.where((e) => e.products?.isNotEmpty == true)
.toList();
_allValue[code ?? ""] = filtered;
topUpNetworkData.assignAll(filtered);
makeSelected(filtered);
},
onFailure: (_, _, _) async {},
);
}
redeemProductMobileCard() async {
Future<void> redeemProductMobileCard() async {
final id = selectedProduct.value?.id;
if (id == null) {
onShowAlertError?.call("Vui lòng chọn sản phẩm");
return;
}
showLoading();
try {
final response = await client.redeemProductTopUps(id, phoneNumber.value);
hideLoading();
if (!response.isSuccess) {
onShowAlertError?.call(response.errorMessage ?? Constants.commonError);
return;
}
final mgs = (response.errorMessage ?? "").isEmpty ? "Chúc mừng bạn đã đổi Ưu đãi data thành công" : (response.errorMessage ?? "");
onShowAlertRedeemSuccess?.call(mgs);
} catch (error) {
hideLoading();
onShowAlertError?.call(error.toString());
showToastMessage('Vui lòng chọn sản phẩm');
return;
}
await callApi<EmptyCodable>(
request: () => client.redeemProductTopUps(id, phoneNumber.value),
onSuccess: (data, _) {
onShowAlertRedeemSuccess?.call("Chúc mừng bạn đã đổi Ưu đãi data thành công");
},
onFailure: (msg, _, _) async {
onShowAlertError?.call(msg.isNotEmpty ? msg : Constants.commonError);
},
);
}
}
\ No newline at end of file
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import 'package:mypoint_flutter_app/widgets/custom_toast_message.dart';
import '../../networking/restful_api_viewmodel.dart';
import '../../configs/constants.dart';
import '../otp/delete_account_otp_repository.dart';
import '../otp/model/create_otp_response_model.dart';
import '../otp/otp_screen.dart';
import '../otp/verify_otp_repository.dart';
class DeleteAccountViewModel extends RestfulApiViewModel {
RxBool agreed = false.obs;
final RxBool agreed = false.obs;
void confirmDelete() {
if (agreed.value) {
showLoading();
client.requestOtpDeleteAccount().then((value) {
hideLoading();
if (value.isSuccess) {
final phone = DataPreference.instance.phone ?? "";
Get.to(
() => OtpScreen(repository: DeleteAccountOtpRepository(phone, value.data?.resendAfterSecond ?? Constants.otpTtl)),
);
} else {
final mgs = value.errorMessage ?? Constants.commonError;
Get.snackbar("Thông báo", mgs);
}
});
} else {
Get.snackbar("Thông báo", "Bạn cần đồng ý với điều khoản để tiếp tục.");
Future<void> confirmDelete() async {
if (!agreed.value) {
showToastMessage("Bạn cần đồng ý với điều khoản để tiếp tục.");
return;
}
await callApi<CreateOTPResponseModel>(
request: () => client.requestOtpDeleteAccount(),
onSuccess: (data, _) {
final phone = DataPreference.instance.phone ?? "";
final ttl = data.resendAfterSecond ?? Constants.otpTtl;
Get.to(() => OtpScreen(repository: DeleteAccountOtpRepository(phone, ttl)));
},
onFailure: (msg, _, _) async {
showToastMessage(msg);
},
);
}
}
......@@ -5,54 +5,51 @@ import '../../networking/restful_api_viewmodel.dart';
import 'device_manager_model.dart';
class DeviceManagerViewModel extends RestfulApiViewModel {
var logoutDevicesResponse = Rxn<DevicesLogoutListResponse>();
var currentDevice = Rxn<DeviceItemModel>();
final Rxn<DevicesLogoutListResponse> logoutDevicesResponse = Rxn<DevicesLogoutListResponse>();
final Rxn<DeviceItemModel> currentDevice = Rxn<DeviceItemModel>();
void Function(String message)? onShowAlertError;
getData() {
void getData() {
getLogoutDevicesResponse();
getCurrentDevice();
}
Future<void> getLogoutDevicesResponse() async {
final body = {"page": 0, "limit": 200};
showLoading();
try {
final response = await client.getLogoutDevices(body);
hideLoading();
if (response.isSuccess && response.data != null) {
logoutDevicesResponse.value = response.data;
} else {
onShowAlertError?.call(response.message ?? Constants.commonError);
}
} catch (error) {
hideLoading();
onShowAlertError?.call(Constants.commonError);
}
await callApi<DevicesLogoutListResponse>(
request: () => client.getLogoutDevices(body),
onSuccess: (data, _) {
logoutDevicesResponse.value = data;
},
onFailure: (msg, _, _) async {
onShowAlertError?.call(msg);
},
);
}
Future<void> getCurrentDevice() async {
final response = await client.getCurrentDevice();
if (response.isSuccess && response.data != null) {
currentDevice.value = response.data;
}
await callApi<DeviceItemModel>(
request: () => client.getCurrentDevice(),
onSuccess: (data, _) {
currentDevice.value = data;
},
onFailure: (_, _, _) async {},
withLoading: false,
);
}
Future<void> deleteDevice(DeviceItemModel item) async {
if ((item.deviceKey ?? '').isEmpty) return;
showLoading();
try {
final response = await client.deleteDevice(item.deviceKey ?? '');
hideLoading();
if (response.isSuccess) {
getLogoutDevicesResponse();
onShowAlertError?.call(response.data ?? "Đã xóa thiết bị thành công");
} else {
onShowAlertError?.call(response.message ?? Constants.commonError);
}
} catch (error) {
hideLoading();
onShowAlertError?.call("Error fetching product detail: $error");
}
final key = item.deviceKey ?? '';
if (key.isEmpty) return;
await callApi<String>(
request: () => client.deleteDevice(key),
onSuccess: (data, _) async {
await getLogoutDevicesResponse();
onShowAlertError?.call(data);
},
onFailure: (msg, _, _) async {
onShowAlertError?.call(msg);
},
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
import 'package:mypoint_flutter_app/extensions/num_extension.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart';
import '../../resources/base_color.dart';
......
......@@ -2,48 +2,43 @@ import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/extensions/collection_extension.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
import '../../networking/restful_api_viewmodel.dart';
import '../../configs/constants.dart';
import '../transaction/model/payment_method_model.dart';
import 'models/customer_contract_object_model.dart';
import 'models/electric_payment_response_model.dart';
class ElectricPaymentBillViewModel extends RestfulApiViewModel {
var paymentMethods = RxList<PaymentMethodModel>();
var selectedPaymentMethodIndex = 0.obs;
final RxList<PaymentMethodModel> paymentMethods = <PaymentMethodModel>[].obs;
final RxInt selectedPaymentMethodIndex = 0.obs;
void Function(ElectricPaymentResponseModel data)? customerEvnPaymentGatewayResponse;
void Function(String message)? onShowAlertError;
Future<void> getPaymentMethods() async {
showLoading();
try {
final response = await client.getPreviewPaymentMethods();
hideLoading();
selectedPaymentMethodIndex.value = 0;
paymentMethods.value = response.data ?? [];
} catch (error) {
hideLoading();
}
await callApi<List<PaymentMethodModel>>(
request: () => client.getPreviewPaymentMethods(),
onSuccess: (data, _) {
selectedPaymentMethodIndex.value = 0;
paymentMethods.assignAll(data);
},
onFailure: (_, _, _) async {},
);
}
customerEvnPaymentGatewayRequest(CustomerContractModel bill) async {
final paymentMethod = paymentMethods.value.safe(selectedPaymentMethodIndex.value ?? 0) ?? paymentMethods.firstOrNull;
Future<void> customerEvnPaymentGatewayRequest(CustomerContractModel bill) async {
final paymentMethod = paymentMethods.safe(selectedPaymentMethodIndex.value) ?? paymentMethods.firstOrNull;
final paymentMethodType = paymentMethod?.type?.methodBillEVN ?? '';
if (paymentMethodType.isEmpty) {
onShowAlertError?.call("Vui lòng chọn phương thức thanh toán.");
return;
}
showLoading();
try {
final response = await client.customerEvnPaymentGatewayRequest(bill, paymentMethodType);
hideLoading();
if (response.isSuccess && response.data != null) {
customerEvnPaymentGatewayResponse?.call(response.data!);
} else {
onShowAlertError?.call(response.errorMessage ?? "Lỗi khi thanh toán, vui lòng thử lại sau.");
}
} catch (error) {
hideLoading();
onShowAlertError?.call(Constants.commonError);
}
await callApi<ElectricPaymentResponseModel>(
request: () => client.customerEvnPaymentGatewayRequest(bill, paymentMethodType),
onSuccess: (data, _) {
customerEvnPaymentGatewayResponse?.call(data);
},
onFailure: (msg, _, _) async {
onShowAlertError?.call(msg);
},
defaultError: "Lỗi khi thanh toán, vui lòng thử lại sau.",
);
}
}
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
import '../../networking/restful_api_viewmodel.dart';
import '../../configs/constants.dart';
import 'electric_payment_bill_screen.dart';
import 'models/customer_contract_object_model.dart';
......@@ -10,47 +9,44 @@ class ElectricPaymentViewModel extends RestfulApiViewModel {
final RxList<CustomerContractModel> billContracts = <CustomerContractModel>[].obs;
void customerContractRequestSearch(String maKH) {
showLoading();
client.customerContractRequestSearch(maKH).then((value) {
hideLoading();
final result = value.data;
if (!value.isSuccess) {
onShowAlertError?.call(
value.errorMessage ?? "Không tìm thấy thông tin mã khách hàng, vui lòng kiểm tra và thử lại.",
);
} else if (result != null) {
if ((result.amount ?? 0) == 0) {
callApi<CustomerContractModel>(
request: () => client.customerContractRequestSearch(maKH),
onSuccess: (data, _) {
if ((data.amount ?? 0) == 0) {
onShowAlertError?.call("Bạn đã thanh toán hết hóa đơn.");
} else {
Get.to(ElectricPaymentBillScreen(bill: result,));
Get.to(ElectricPaymentBillScreen(bill: data));
}
} else {
onShowAlertError?.call("Không tìm thấy thông tin mã khách hàng, vui lòng kiểm tra và thử lại.");
}
});
},
onFailure: (msg, _, _) async {
onShowAlertError?.call(msg);
},
defaultError: "Không tìm thấy thông tin mã khách hàng, vui lòng kiểm tra và thử lại."
);
}
void customerContractSearchHistoryGetList() {
showLoading();
client.customerContractSearchHistoryGetList().then((value) {
hideLoading();
final result = value.data;
if (!value.isSuccess && result == null) {
onShowAlertError?.call(value.errorMessage ?? Constants.commonError);
} else {
billContracts.value = result ?? [];
}
});
callApi<List<CustomerContractModel>>(
request: () => client.customerContractSearchHistoryGetList(),
onSuccess: (data, _) {
billContracts.assignAll(data);
},
onFailure: (msg, _, _) async {
billContracts.clear();
onShowAlertError?.call(msg);
},
);
}
void customerContractDelete(String code) {
showLoading();
client.customerContractDelete(code).then((value) {
hideLoading();
if (!value.isSuccess) {
onShowAlertError?.call(value.errorMessage ?? Constants.commonError,);
}
customerContractSearchHistoryGetList();
});
callApi<bool>(
request: () => client.customerContractDelete(code),
onSuccess: (_, _) {
customerContractSearchHistoryGetList();
},
onFailure: (msg, _, _) async {
onShowAlertError?.call(msg);
},
);
}
}
......@@ -3,8 +3,10 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/screen/pageDetail/campaign_detail_screen.dart';
import 'package:mypoint_flutter_app/widgets/back_button.dart';
import 'package:mypoint_flutter_app/widgets/custom_toast_message.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../configs/constants.dart';
import '../../resources/base_color.dart';
import '../../shared/router_gage.dart';
import '../../widgets/custom_navigation_bar.dart';
......@@ -27,9 +29,6 @@ class _FAQScreenState extends BaseState<FAQScreen> with BasicState {
body: Column(
children: [
Obx(() {
if (_controller.isLoading.value) {
return const Expanded(child: Center(child: CircularProgressIndicator()));
}
if (_controller.faqItems.isEmpty) {
return const Expanded(child: Center(child: Text("Không có dữ liệu.")));
}
......@@ -50,12 +49,7 @@ class _FAQScreenState extends BaseState<FAQScreen> with BasicState {
if (item.pageId != null && item.pageId!.isNotEmpty) {
Get.toNamed(campaignDetailScreen, arguments: {"id": item.pageId});
} else {
Get.snackbar(
"Thông báo",
"Không thể mở chi tiết vì thiếu ID!",
backgroundColor: Colors.redAccent,
colorText: Colors.white,
);
showToastMessage(ErrorCodes.serverErrorMessage);
}
},
child: Column(
......
import 'dart:convert';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
import '../../networking/restful_api_viewmodel.dart';
import 'faqs_model.dart';
class FAQViewModel extends RestfulApiViewModel {
var faqItems = <PageItemModel>[].obs;
var isLoading = true.obs;
final RxList<PageItemModel> faqItems = <PageItemModel>[].obs;
@override
void onInit() {
......@@ -15,12 +13,14 @@ class FAQViewModel extends RestfulApiViewModel {
}
Future<void> fetchFAQItems() async {
showLoading();
isLoading(true);
client.websiteFolderGetPageList({"folder_uri": "FAQ"}).then((value) {
hideLoading();
isLoading(false);
faqItems.value = value.data?.items ?? [];
});
await callApi<FAQItemModelResponse>(
request: () => client.websiteFolderGetPageList({"folder_uri": "FAQ"}),
onSuccess: (data, _) {
faqItems.assignAll(data.items ?? []);
},
onFailure: (_, _, _) async {
faqItems.clear();
},
);
}
}
......@@ -67,9 +67,6 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState, Popu
],
),
body: Obx(() {
if (_viewModel.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
if (_viewModel.games.isEmpty) {
return const Center(child: EmptyWidget());
}
......
......@@ -4,38 +4,36 @@ import 'package:mypoint_flutter_app/screen/game/models/game_bundle_item_model.da
import '../../networking/restful_api_viewmodel.dart';
import '../../configs/constants.dart';
import 'models/game_bundle_response.dart';
class GameTabViewModel extends RestfulApiViewModel {
final RxList<GameBundleItemModel> games = <GameBundleItemModel>[].obs;
var turnsNumberText = "".obs;
var isLoading = false.obs;
final RxString turnsNumberText = "".obs;
void Function(String message)? onShowAlertError;
void Function(GameBundleItemModel data)? gotoGameDetail;
void getGames() {
isLoading(true);
client.getGames().then((value) {
if (!value.isSuccess) {
onShowAlertError?.call(value.errorMessage ?? Constants.commonError);
} else {
games.value = value.data?.games ?? [];
turnsNumberText.value = value.data?.turnsNumberText ?? "";
}
isLoading(false);
});
callApi<GameBundleResponse>(
request: () => client.getGames(),
onSuccess: (data, _) {
games.assignAll(data.games ?? []);
turnsNumberText.value = data.turnsNumberText ?? "";
},
onFailure: (msg, _, _) async {
onShowAlertError?.call(msg);
},
);
}
void getGameDetail(String gameId) {
isLoading(true);
client.getGameDetail(gameId).then((value) {
if (!value.isSuccess) {
onShowAlertError?.call(value.errorMessage ?? Constants.commonError);
} else if (value.data != null) {
gotoGameDetail?.call(value.data!);
} else {
onShowAlertError?.call(Constants.commonError);
}
isLoading(false);
});
callApi<GameBundleItemModel>(
request: () => client.getGameDetail(gameId),
onSuccess: (data, _) {
gotoGameDetail?.call(data);
},
onFailure: (msg, _, _) async {
onShowAlertError?.call(msg);
},
);
}
}
\ No newline at end of file
......@@ -5,17 +5,18 @@ import '../../networking/restful_api_viewmodel.dart';
import 'health_book_model.dart';
class HealthBookCardDetailViewModel extends RestfulApiViewModel {
var card = Rxn<HealthBookCardItemModel>();
final Rxn<HealthBookCardItemModel> card = Rxn<HealthBookCardItemModel>();
void Function(String message)? onShowAlertError;
Future<void> getHealthBookCardDetail(String cardId) async {
showLoading();
final response = await client.getDetailHealthBookCard(cardId);
hideLoading();
if (response.isSuccess) {
card.value = response.data;
} else {
onShowAlertError?.call(response.errorMessage ?? Constants.commonError);
}
await callApi<HealthBookCardItemModel>(
request: () => client.getDetailHealthBookCard(cardId),
onSuccess: (data, _) {
card.value = data;
},
onFailure: (msg, _, _) async {
onShowAlertError?.call(msg);
},
);
}
}
\ No newline at end of file
......@@ -6,10 +6,10 @@ import '../../networking/restful_api_viewmodel.dart';
import 'health_book_model.dart';
class HealthBookViewModel extends RestfulApiViewModel {
var healthBookData = Rxn<HealthBookResponseModel>();
var healthBookDataDetail = Rxn<HealthBookCardItemModel>();
final Rxn<HealthBookResponseModel> healthBookData = Rxn<HealthBookResponseModel>();
final Rxn<HealthBookCardItemModel> healthBookDataDetail = Rxn<HealthBookCardItemModel>();
void Function(String message)? onShowAlertError;
RxInt selectedIndex = 0.obs;
final RxInt selectedIndex = 0.obs;
late List<HeaderFilterOrderModel> headerFilterOrder;
@override
......@@ -45,58 +45,14 @@ class HealthBookViewModel extends RestfulApiViewModel {
var body = headerFilterOrder[selectedIndex.value].params;
body['page'] = 1;
body['size'] = 10000;
showLoading();
try {
final response = await client.getHealthBookCards(body);
hideLoading();
// var data = HealthBookResponseModel(total: 20, products: makeFakeHealthBookCards(20));
// healthBookData.value = data;
if (response.isSuccess) {
healthBookData.value = response.data;
} else {
onShowAlertError?.call(response.errorMessage ?? Constants.commonError);
}
} catch (error) {
hideLoading();
onShowAlertError?.call("Error fetching product detail: $error");
}
await callApi<HealthBookResponseModel>(
request: () => client.getHealthBookCards(body),
onSuccess: (data, _) {
healthBookData.value = data;
},
onFailure: (msg, _, _) async {
onShowAlertError?.call(msg);
},
);
}
// Future<void> getDetailHealthBookCard(String id) async {
// showLoading();
// try {
// final response = await client.getDetailHealthBookCard(id);
// hideLoading();
// if (response.isSuccess) {
// healthBookDataDetail.value = response.data;
// } else {
// onShowAlertError?.call(response.errorMessage ?? Constants.commonError);
// }
// } catch (error) {
// hideLoading();
// onShowAlertError?.call("Error fetching product detail: $error");
// }
// }
// List<HealthBookCardItemModel> makeFakeHealthBookCards(int n) {
// return List.generate(n, (i) {
// final id = 2000 + i;
// return HealthBookCardItemModel(
// itemId: id,
// cardName: 'Thẻ Khám #$id',
// fullName: 'User #$id',
// cardCode: 'MP-${id.toString().padLeft(4, '0')}',
// expireDate: '2025-12-31T00:00:00Z',
// phoneNumber: '09${(10000000 + i).toString().padLeft(8, '0')}',
// updatedAt: '2025-10-10T10:00:00Z',
// countCheckupUnused: i % 6,
// bottomButton: ButtonConfigModel(text: 'Sử dụng', action: 'use'),
// media: [
// ProductMediaItem(url: 'https://picsum.photos/seed/$id/300/300'),
// ],
// buyMoreNote: ButtonConfigModel(text: 'Mua thêm', action: 'buy_more'),
// active: ActiveTextConfig(text: 'Còn hiệu lực', textColor: '#0F9D58', bgColor: '#E6F4EA'),
// );
// });
// }
}
\ No newline at end of file
......@@ -119,3 +119,4 @@ class HealthBookItem extends StatelessWidget {
);
}
}
......@@ -3,6 +3,7 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:intl/intl.dart';
import 'package:mypoint_flutter_app/extensions/num_extension.dart';
import 'models/transaction_summary_by_date_model.dart';
class MonthlyPointsChart extends StatelessWidget {
......@@ -26,12 +27,21 @@ class MonthlyPointsChart extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('🔍 MonthlyPointsChart Debug:');
print(' - items.length: ${items.length}');
print(' - date: $date');
print(' - monthSummary: $monthSummary');
for (int i = 0; i < items.length; i++) {
print(' - items[$i]: ${items[i].summaryDate} -> reward: ${items[i].rewardDayTotal}');
}
final parsed = _parseToDayMap(items, date);
print('items ${items.length}, parsed: $parsed');
print(' - parsed map: $parsed');
final daysInMonth = DateUtils.getDaysInMonth(date.year, date.month);
final maxVal = (parsed.values.isEmpty ? 0 : parsed.values.reduce((a, b) => a > b ? a : b)).abs();
final yMax = _niceMax(maxVal.toDouble());
final maxVal = parsed.values.isEmpty ? 0 : parsed.values.reduce((a, b) => a > b ? a : b);
final yMax = _niceMax(maxVal.abs().toDouble());
final yStep = _niceStep(yMax);
final stats = _statsByDay(items, date);
final barGroups = List.generate(daysInMonth, (i) {
final day = i + 1;
......@@ -42,6 +52,12 @@ class MonthlyPointsChart extends StatelessWidget {
barRods: [BarChartRodData(toY: v, width: 8, borderRadius: BorderRadius.circular(2), color: color)],
);
});
print(' - daysInMonth: $daysInMonth');
print(' - maxVal: $maxVal');
print(' - yMax: $yMax');
print(' - yStep: $yStep');
print(' - barGroups.length: ${barGroups.length}');
return Container(
decoration: BoxDecoration(
......@@ -62,7 +78,7 @@ class MonthlyPointsChart extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_formatInt(_toDouble(monthSummary?.rewardMonthTotal ?? '0')),
_toDouble(monthSummary?.rewardMonthTotal ?? '0').money(CurrencyUnit.noneSpace),
style: const TextStyle(
fontSize: 40,
fontWeight: FontWeight.w700,
......@@ -80,19 +96,19 @@ class MonthlyPointsChart extends StatelessWidget {
child: BarChart(
BarChartData(
minY: 0,
maxY: yMax,
maxY: yMax > 0 ? yMax : 10, // Đảm bảo có giá trị tối thiểu
barGroups: barGroups,
gridData: FlGridData(
show: true,
drawVerticalLine: false,
horizontalInterval: yStep,
horizontalInterval: yStep > 0 ? yStep : 2,
getDrawingHorizontalLine: (v) => FlLine(color: Colors.black12, strokeWidth: 1),
),
extraLinesData: ExtraLinesData(
extraLinesOnTop: true,
horizontalLines: [
HorizontalLine(y: 0, color: Colors.black12, strokeWidth: 1),
HorizontalLine(y: yMax, color: Colors.black12, strokeWidth: 1),
HorizontalLine(y: yMax > 0 ? yMax : 10, color: Colors.black12, strokeWidth: 1),
],
),
titlesData: FlTitlesData(
......@@ -100,14 +116,22 @@ class MonthlyPointsChart extends StatelessWidget {
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
leftTitles: AxisTitles(
sideTitles: SideTitles(
reservedSize: 28,
reservedSize: 40, // Tăng độ rộng để chứa text dài
showTitles: true,
interval: yStep,
getTitlesWidget:
(value, meta) => Text(
value.toInt().toString(),
style: const TextStyle(fontSize: 10, color: Colors.black54),
interval: yStep > 0 ? yStep : 2,
getTitlesWidget: (value, meta) {
final text = _formatYAxisLabel(value.toInt());
return SizedBox(
width: 35, // Cố định độ rộng
child: Text(
text,
style: const TextStyle(fontSize: 9, color: Colors.black54),
textAlign: TextAlign.right,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
);
},
),
),
bottomTitles: AxisTitles(
......@@ -143,7 +167,7 @@ class MonthlyPointsChart extends StatelessWidget {
return BarTooltipItem(
textAlign: TextAlign.center,
'Ngày $day/${date.month}\n'
'Tích điểm: ${_formatInt(r)}',
'Tích điểm: ${r.money(CurrencyUnit.noneSpace)}',
const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.w500),
);
},
......@@ -222,12 +246,31 @@ class MonthlyPointsChart extends StatelessWidget {
}
static String _formatInt(double v) => v.toStringAsFixed(0);
/// Format Y-axis label để hiển thị gọn gàng
String _formatYAxisLabel(int value) {
if (value >= 1000000) {
return '${(value / 1000000).toStringAsFixed(1)}M';
} else if (value >= 1000) {
return '${(value / 1000).toStringAsFixed(0)}K';
} else {
return value.toString();
}
}
/// Làm tròn max Y cho đẹp (bước 4/5)
double _niceMax(double maxVal) {
if (maxVal <= 0) return 10;
// làm tròn lên tới bội số 4 hoặc 5 gần nhất
final candidates = [4, 5, 10];
// Xử lý giá trị lớn (> 1000)
if (maxVal > 1000) {
// Làm tròn lên đến bội số 1000 gần nhất
final up = ((maxVal / 1000).ceil()) * 1000;
return up.toDouble();
}
// Xử lý giá trị nhỏ (< 1000)
final candidates = [4, 5, 10, 20, 50, 100];
for (final step in candidates) {
final up = ((maxVal / step).ceil()) * step;
if (up >= maxVal && up / step <= 6) return up.toDouble(); // tối đa 6 vạch cho gọn
......@@ -239,7 +282,16 @@ class MonthlyPointsChart extends StatelessWidget {
if (yMax <= 10) return 2; // 0,2,4,6,8,10
if (yMax <= 20) return 4; // 0,4,8,12,16,20
if (yMax <= 50) return 10;
return 20;
if (yMax <= 100) return 20;
if (yMax <= 500) return 50;
if (yMax <= 1000) return 100;
if (yMax <= 5000) return 500;
if (yMax <= 10000) return 1000;
if (yMax <= 50000) return 5000;
if (yMax <= 100000) return 10000;
if (yMax <= 500000) return 50000;
if (yMax <= 1000000) return 100000;
return 200000; // cho giá trị rất lớn
}
Map<int, _DayStat> _statsByDay(List<DaySummaryChartModel> list, DateTime month) {
......
......@@ -202,7 +202,7 @@ class _HistoryPointScreenState extends State<HistoryPointScreen> {
final adjustTotal = item.adjustTotal?.toInt() ?? 0;
final value = rewardTotal - redeemTotal + adjustTotal;
final valueColor = value >= 0 ? const Color(0xFF21C777) : const Color(0xFFFE515A);
final valueText = '${value > 0 ? '+' : (value < 0 ? '-' : '')}${value.toInt()}';
final valueText = '${value > 0 ? '+' : (value < 0 ? '-' : '')}${value.money(CurrencyUnit.noneSpace)}';
final dateText = item.transactionDatetime?.toDate()?.toFormattedString(format: DateFormat.viFull);
final transactionId = item.transactionSequenceId.orIfBlank('');
return InkWell(
......
......@@ -40,7 +40,7 @@ class HistoryPointViewModel extends RestfulApiViewModel {
onSuccess: (data, _) {
transactionSummary.value = data;
},
onFailure: (msg, _, __) async {
onFailure: (msg, _, _) async {
transactionSummary.value = null;
},
);
......@@ -62,7 +62,7 @@ class HistoryPointViewModel extends RestfulApiViewModel {
onSuccess: (data, _) {
historyPoint.value = data;
},
onFailure: (msg, _, __) async {
onFailure: (msg, _, _) async {
historyPoint.value = null;
},
);
......
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/extensions/collection_extension.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
import '../../networking/restful_api_viewmodel.dart';
import 'models/history_point_cashback_model.dart';
class HistoryPointCashBackViewModel extends RestfulApiViewModel {
late List<CashBackPointOrderStatus> tagStatus;
CashBackPointOrderStatus get selectedTag {
return tagStatus.safe(selectedTabIndex.value) ?? CashBackPointOrderStatus.pending;
}
CashBackPointOrderStatus get selectedTag => tagStatus.safe(selectedTabIndex.value) ?? CashBackPointOrderStatus.pending;
final RxInt selectedTabIndex = 0.obs;
var pointCashBackData = Rxn<HistoryPointCashBackResponse>();
List<HistoryPointCashBackOrderModel> get orders {
return pointCashBackData.value?.orders ?? [];
}
final Rxn<HistoryPointCashBackResponse> pointCashBackData = Rxn<HistoryPointCashBackResponse>();
List<HistoryPointCashBackOrderModel> get orders => pointCashBackData.value?.orders ?? [];
int _page = 1;
......@@ -31,28 +26,23 @@ class HistoryPointCashBackViewModel extends RestfulApiViewModel {
freshData(isRefresh: true);
}
void freshData({bool isRefresh = false}) {
if (isRefresh) {
_page = 1;
} else {
_page += 1;
}
Future<void> freshData({bool isRefresh = false}) async {
_page = isRefresh ? 1 : _page + 1;
final body = {"page": _page, "size": 20, "type": selectedTag.rawValue};
client
.historyPointCashBackRequest(body)
.then((response) {
final result = response.data;
if (isRefresh) {
pointCashBackData.value = result;
} else {
final orders = result?.orders ?? [];
pointCashBackData.value?.orders?.addAll(orders);
pointCashBackData.refresh();
}
})
.catchError((error) {
print('Error fetching products: $error');
});
await callApi<HistoryPointCashBackResponse>(
request: () => client.historyPointCashBackRequest(body),
onSuccess: (data, _) {
if (isRefresh) {
pointCashBackData.value = data;
} else {
final next = data.orders ?? [];
pointCashBackData.value?.orders?.addAll(next);
pointCashBackData.refresh();
}
},
onFailure: (_, _, _) async {},
withLoading: isRefresh,
);
}
void selectTab(int index) {
......
......@@ -20,7 +20,6 @@ class HomeGreetingHeader extends StatelessWidget {
final heightSize = heightContent ?? (width * 86 / 375 + 112);
final name = DataPreference.instance.displayName;
final level = DataPreference.instance.rankName ?? "Hạng Đồng";
final double statusBarHeight = MediaQuery.of(context).padding.top;
final double heightWhiteBox = 112;
return Stack(
......@@ -149,23 +148,23 @@ class HomeGreetingHeader extends StatelessWidget {
);
}
_onSearchTap() {
void _onSearchTap() {
Get.toNamed(vouchersScreen, arguments: {"enableSearch": true});
}
_onNotificationTap() {
void _onNotificationTap() {
Get.toNamed(notificationScreen);
}
_onPointTap() {
void _onPointTap() {
Get.toNamed(historyPointScreen);
}
_onMyVoucherTap() {
void _onMyVoucherTap() {
Get.toNamed(myVoucherListScreen);
}
_onRankTap() {
void _onRankTap() {
Get.toNamed(membershipScreen);
}
}
......@@ -11,7 +11,7 @@ class HeaderThemeController extends GetxController {
class HeaderHomeViewModel extends RestfulApiViewModel {
final Rx<HeaderHomeModel?> _headerHomeData = Rx<HeaderHomeModel?>(null);
var notificationUnreadData = Rxn<NotificationUnreadData>();
final Rxn<NotificationUnreadData> notificationUnreadData = Rxn<NotificationUnreadData>();
HeaderHomeModel get headerData {
return _headerHomeData.value ??
......@@ -32,21 +32,23 @@ class HeaderHomeViewModel extends RestfulApiViewModel {
}
Future<void> _getDynamicHeaderHome() async {
try {
final result = await client.getDynamicHeaderHome();
_headerHomeData.value = result.data;
Get.find<HeaderThemeController>().setBackground(_headerHomeData.value?.background);
} catch (error) {
print("Error fetching getDynamicHeaderHome: $error");
}
await callApi<HeaderHomeModel>(
request: () => client.getDynamicHeaderHome(),
onSuccess: (data, _) {
_headerHomeData.value = data;
Get.find<HeaderThemeController>().setBackground(_headerHomeData.value?.background);
},
withLoading: false,
);
}
Future<void> _getNotificationUnread() async {
try {
final result = await client.getNotificationUnread();
notificationUnreadData.value = result.data;
} catch (error) {
print("Error fetching hot products: $error");
}
await callApi<NotificationUnreadData>(
request: () => client.getNotificationUnread(),
onSuccess: (data, _) {
notificationUnreadData.value = data;
},
withLoading: false,
);
}
}
......@@ -6,7 +6,6 @@ import 'package:mypoint_flutter_app/screen/pipi/pipi_detail_screen.dart';
import 'package:mypoint_flutter_app/screen/voucher/models/product_model.dart';
import 'package:mypoint_flutter_app/shared/router_gage.dart';
import '../../directional/directional_action_type.dart';
import '../../directional/directional_screen.dart';
import '../popup_manager/popup_runner_helper.dart';
import 'custom_widget/achievement_carousel_widget.dart';
import 'custom_widget/affiliate_brand_grid_widget.dart';
......
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