Commit e9cf8244 authored by DatHV's avatar DatHV
Browse files

update fix bug, handle error.

parent a0bcdab2
......@@ -14,6 +14,7 @@ import '../directional/directional_screen.dart';
import '../model/auth/biometric_register_response_model.dart';
import '../model/auth/login_token_response_model.dart';
import '../model/auth/profile_response_model.dart';
import '../preference/package_info.dart';
import '../screen/health_book/health_book_model.dart';
import '../screen/history_point/models/history_point_models.dart';
import '../screen/history_point/models/transaction_summary_by_date_model.dart';
......@@ -82,9 +83,10 @@ import '../screen/voucher/models/search_product_response_model.dart';
extension RestfulAPIClientAllRequest on RestfulAPIClient {
Future<BaseResponseModel<UpdateResponseModel>> checkUpdateApp() async {
final operatingSystem = kIsWeb ? "web" : Platform.operatingSystem;
final version = kIsWeb ? "1.0.0" : Platform.version;
final body = {"operating_system": operatingSystem, "software_model": "MyPoint", "version": version, "build_number": "1"};
final operatingSystem = Platform.operatingSystem;
final version = await AppInfoHelper.version;
final buildNumber = await AppInfoHelper.buildNumber;
final body = {"operating_system": operatingSystem, "software_model": "MyPoint", "version": version, "build_number": buildNumber};
return requestNormal(APIPaths.checkUpdate, Method.POST, body, (data) => UpdateResponseModel.fromJson(data as Json));
}
......
......@@ -5,13 +5,20 @@ import '../base/base_response_model.dart';
import '../base/base_view_model.dart';
import '../configs/constants.dart';
import '../base/app_navigator.dart';
import 'dio_extra_keys.dart';
import 'dio_http_service.dart';
import 'error_mapper.dart';
import 'interceptor/network_error_gate.dart';
typedef ApiCall<T> = Future<BaseResponseModel<T>> Function();
typedef OnSuccess<T> = FutureOr<void> Function(T data, BaseResponseModel<T> res);
typedef OnFailure<T> = FutureOr<void> Function(String message, BaseResponseModel<T>? res, Object? error);
typedef OnSuccess<T> =
FutureOr<void> Function(T data, BaseResponseModel<T> res);
typedef OnFailure<T> =
FutureOr<void> Function(
String message,
BaseResponseModel<T>? res,
Object? error,
);
typedef OnComplete = FutureOr<void> Function();
typedef ApiTask = Future<void> Function();
......@@ -39,43 +46,47 @@ class RestfulApiViewModel extends BaseViewModel {
final msg = res.errorMessage ?? defaultError;
final hasInternet = await NetworkConnectivity().hasInternet();
if (showAppNavigatorDialog) {
AppNavigator.showAlertError(content: hasInternet ? msg : ErrorCodes.networkError);
AppNavigator.showAlertError(
content: hasInternet ? msg : ErrorCodes.networkError,
);
} else {
await onFailure?.call(hasInternet ? msg : ErrorCodes.networkError, res, null);
await onFailure?.call(
hasInternet ? msg : ErrorCodes.networkError,
res,
null,
);
}
}
} catch (e) {
if (e is DioException &&
e.requestOptions.extra[kExtraSkipApiErrorHandling] == true) {
return;
}
String msg = defaultError;
if (e is DioException) {
final mapped = e.requestOptions.extra['mapped_error'];
msg = (mapped is String && mapped.isNotEmpty) ? mapped : ErrorMapper.map(e);
msg =
(mapped is String && mapped.isNotEmpty)
? mapped
: ErrorMapper.map(e);
} else {
msg = ErrorMapper.map(e);
}
final hasInternet = await NetworkConnectivity().hasInternet();
if (showAppNavigatorDialog) {
AppNavigator.showAlertError(content: hasInternet ? msg : ErrorCodes.networkError);
AppNavigator.showAlertError(
content: hasInternet ? msg : ErrorCodes.networkError,
);
} else {
await onFailure?.call(hasInternet ? msg : ErrorCodes.networkError, res, null);
await onFailure?.call(
hasInternet ? msg : ErrorCodes.networkError,
res,
null,
);
}
} finally {
if (withLoading) hideLoading();
onComplete?.call();
}
}
Future<void> runAllApis({
required List<ApiTask> tasks,
OnComplete? onComplete,
bool withLoading = true,
}) async {
if (withLoading) showLoading();
try {
final futures = tasks.map((t) => t()).toList();
await Future.wait(futures);
} finally {
if (withLoading) hideLoading();
onComplete?.call();
}
}
}
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
import '../../networking/restful_api_viewmodel.dart';
import '../data_preference.dart';
import 'header_home_model.dart';
class UserPointManager extends RestfulApiViewModel {
static final UserPointManager _instance = UserPointManager._internal();
import '../../screen/home/header_home_viewmodel.dart';
class UserPointManager {
UserPointManager._();
static final UserPointManager _instance = UserPointManager._();
factory UserPointManager() => _instance;
UserPointManager._internal();
final RxInt _userPoint = 0.obs;
HeaderHomeModel? _headerInfo;
final HeaderHomeRepository _repository = HeaderHomeRepository();
final RxInt _point = 0.obs;
RxInt get pointStream => _point;
int get point => _point.value;
int get point => _userPoint.value;
void setPoint(int value) {
if (point == value) return;
_point.value = value;
}
Future<int?> fetchUserPoint() async {
if (!DataPreference.instance.logged) return null;
try {
final response = await client.getHomeHeaderData();
if (response.isSuccess && response.data != null) {
_headerInfo = response.data;
_userPoint.value = _headerInfo?.totalPointActive ?? 0;
return _userPoint.value;
} else {
_userPoint.value = 0;
return null;
}
} catch (e) {
_userPoint.value = 0;
return null;
}
Future<int?> fetchUserPoint({bool withLoading = false}) async {
await _repository.fetchHeader(withLoading: withLoading);
return _point.value;
}
}
......@@ -140,12 +140,11 @@ class _AffiliateBrandDetailScreenState extends BaseState<AffiliateBrandDetailScr
return Stack(
clipBehavior: Clip.none,
children: [
loadNetworkImage(
url: _viewModel.brandDetailData.value?.background,
Image.asset(
'assets/images/bg_header_detail_brand.png',
fit: BoxFit.cover,
height: imageHeight,
width: double.infinity,
placeholderAsset: 'assets/images/bg_header_detail_brand.png',
),
Positioned(
left: 0,
......
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart';
......@@ -56,14 +58,15 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState, Popu
title: "Games",
leftButtons: _canBackButton ? [CustomBackButton()] : [],
rightButtons: [
CompositedTransformTarget(
link: _layerLink,
child: IconButton(
key: _infoKey,
icon: const Icon(Icons.info, color: Colors.white),
onPressed: _togglePopup,
if (Platform.isIOS)
CompositedTransformTarget(
link: _layerLink,
child: IconButton(
key: _infoKey,
icon: const Icon(Icons.info, color: Colors.white),
onPressed: _togglePopup,
),
),
),
],
),
body: Obx(() {
......
......@@ -122,3 +122,5 @@ class HealthBookItem extends StatelessWidget {
......@@ -111,7 +111,7 @@ class _HistoryPointScreenState extends State<HistoryPointScreen> {
);
}
_showDatePicker() {
void _showDatePicker() {
showMonthPicker(context: context, initialDate: _viewModel.selectedDate, lastDate: DateTime.now()).then((date) {
if (date == null) return;
_viewModel.selectedDate = date;
......@@ -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.money(CurrencyUnit.noneSpace)}';
final valueText = '${value > 0 ? '+' : (value < 0 ? '-' : '')}${value.abs().money(CurrencyUnit.noneSpace)}';
final dateText = item.transactionDatetime?.toDate()?.toFormattedString(format: DateFormat.viFull);
final transactionId = item.transactionSequenceId.orIfBlank('');
return InkWell(
......
......@@ -4,6 +4,7 @@ import 'package:mypoint_flutter_app/extensions/num_extension.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart';
import '../../../preference/data_preference.dart';
import '../../../preference/point/header_home_model.dart';
import '../../../preference/point/point_manager.dart';
import '../../../shared/router_gage.dart';
import '../models/notification_unread_model.dart';
......@@ -107,11 +108,14 @@ class HomeGreetingHeader extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
_buildStatItem(
icon: "assets/images/ic_point_gray.png",
value: (dataHeader.totalPointActive ?? 0).money(CurrencyUnit.none),// .toString(),
onTap: _onPointTap,
),
Obx(() {
final point = UserPointManager().pointStream.value;
return _buildStatItem(
icon: "assets/images/ic_point_gray.png",
value: point.money(CurrencyUnit.none),
onTap: _onPointTap,
);
}),
const SizedBox(width: 8),
_buildStatItem(
icon: "assets/images/ic_voucher_gray.png",
......
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 '../../networking/restful_api_viewmodel.dart';
import '../../preference/point/point_manager.dart';
import '../../preference/point/header_home_model.dart';
import 'models/notification_unread_model.dart';
class HeaderThemeController extends GetxController {
final background = RxnString();
void setBackground(String? url) => background.value = url;
}
class HeaderHomeRepository extends RestfulApiViewModel {
HeaderHomeRepository._();
static final HeaderHomeRepository _instance = HeaderHomeRepository._();
factory HeaderHomeRepository() => _instance;
class HeaderHomeViewModel extends RestfulApiViewModel {
final Rx<HeaderHomeModel?> _headerHomeData = Rx<HeaderHomeModel?>(null);
final Rxn<NotificationUnreadData> notificationUnreadData = Rxn<NotificationUnreadData>();
HeaderHomeModel get headerData {
return _headerHomeData.value ??
HeaderHomeModel(
greeting: 'Xin chào!',
totalVoucher: 0,
totalPointActive: 0,
background: '',
);
}
final Rx<HeaderHomeModel?> _headerHome = Rx<HeaderHomeModel?>(null);
final Rxn<NotificationUnreadData> _notificationUnread = Rxn<NotificationUnreadData>();
int get totalPoint => header.totalPointActive ?? 0;
Future<void> freshData() async {
await _getDynamicHeaderHome();
await _getNotificationUnread();
HeaderHomeModel get header => _headerHome.value ??
HeaderHomeModel(
greeting: 'Xin chào!',
totalVoucher: 0,
totalPointActive: 0,
background: '',
);
Rx<HeaderHomeModel?> get headerStream => _headerHome;
Rxn<NotificationUnreadData> get notificationStream => _notificationUnread;
Future<void> _load({bool withLoading = false}) async {
await Future.wait([
fetchHeader(withLoading: withLoading),
_fetchNotificationUnread(),
]);
}
Future<void> _getDynamicHeaderHome() async {
Future<void> fetchHeader({bool withLoading = false}) async {
if (!DataPreference.instance.logged) return;
await callApi<HeaderHomeModel>(
request: () => client.getDynamicHeaderHome(),
onSuccess: (data, _) {
_headerHomeData.value = data;
Get.find<HeaderThemeController>().setBackground(_headerHomeData.value?.background);
_headerHome.value = data;
UserPointManager().setPoint(data.totalPointActive ?? 0);
},
withLoading: false,
withLoading: withLoading,
);
}
Future<void> _getNotificationUnread() async {
Future<void> _fetchNotificationUnread() async {
if (!DataPreference.instance.logged) return;
await callApi<NotificationUnreadData>(
request: () => client.getNotificationUnread(),
onSuccess: (data, _) {
notificationUnreadData.value = data;
_notificationUnread.value = data;
},
withLoading: false,
);
}
}
class HeaderThemeController extends GetxController {
final background = RxnString();
void setBackground(String? url) => background.value = url;
}
class HeaderHomeViewModel extends GetxController {
final HeaderHomeRepository _repository = HeaderHomeRepository();
HeaderHomeModel get headerData => _repository.header;
Rxn<NotificationUnreadData> get notificationUnreadData => _repository.notificationStream;
@override
void onInit() {
super.onInit();
ever<HeaderHomeModel?>(_repository.headerStream, (data) {
if (data?.background != null) {
Get.find<HeaderThemeController>().setBackground(data?.background);
}
});
}
Future<void> freshData() => _repository._load(withLoading: false);
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/preference/point/point_manager.dart';
import 'package:mypoint_flutter_app/screen/home/custom_widget/header_home_widget.dart';
import 'package:mypoint_flutter_app/screen/home/custom_widget/product_grid_widget.dart';
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 '../../widgets/custom_empty_widget.dart';
import '../popup_manager/popup_runner_helper.dart';
import 'custom_widget/achievement_carousel_widget.dart';
import 'custom_widget/affiliate_brand_grid_widget.dart';
......@@ -19,6 +21,7 @@ import 'custom_widget/news_carousel_widget.dart';
import 'header_home_viewmodel.dart';
import 'home_tab_viewmodel.dart';
import 'models/header_section_type.dart';
import 'models/main_section_config_model.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
......@@ -35,6 +38,7 @@ class _HomeScreenState extends State<HomeScreen> with PopupOnInit {
@override
void initState() {
super.initState();
UserPointManager().fetchUserPoint();
_headerHomeVM.freshData();
runPopupCheck(DirectionalScreenName.home);
}
......@@ -53,137 +57,92 @@ class _HomeScreenState extends State<HomeScreen> with PopupOnInit {
}
List<Widget> _buildSectionContent() {
final List<Widget> sections = [];
for (var section in _viewModel.sectionLayouts.value) {
switch (section.headerSectionType) {
case HeaderSectionType.banner:
if (_viewModel.banners.isNotEmpty) {
sections.add(
BannerCarousel(
banners: _viewModel.banners,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.banner),
onTap: (item) {
item.directionalScreen?.begin();
},
),
);
}
break;
case HeaderSectionType.topButton:
if (_viewModel.services.isNotEmpty) {
sections.add(
MainServiceGrid(
services: _viewModel.services,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.topButton),
onTap: (item) {
item.directionalScreen?.begin();
},
),
);
}
break;
case HeaderSectionType.campaign:
if (_viewModel.achievements.isNotEmpty) {
sections.add(
AchievementCarousel(
items: _viewModel.achievements,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.campaign),
onTap: (item) {
item.directionScreen?.begin();
},
),
);
}
break;
case HeaderSectionType.product:
if (_viewModel.products.isNotEmpty) {
List<ProductModel> products = _viewModel.products;
final length = products.length;
products = (length.isOdd) ? products.sublist(0, length - 1) : products;
sections.add(
ProductGrid(
products: products,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.product),
onTap: (product) {
Get.toNamed(voucherDetailScreen, arguments: product.id);
},
),
);
}
break;
case HeaderSectionType.news:
if (_viewModel.news.isNotEmpty) {
sections.add(
NewsCarouselWidget(
items: _viewModel.news,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.news),
onTap: (item) async {
Get.toNamed(campaignDetailScreen, arguments: {"id": item.pageId});
},
),
);
}
break;
case HeaderSectionType.myProduct:
if (_viewModel.myProducts.isNotEmpty) {
sections.add(
MyProductCarouselWidget(
items: _viewModel.myProducts,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.myProduct),
onTap: (item) async {
Get.toNamed(voucherDetailScreen, arguments: {"customerProductId": item.id});
},
),
);
}
break;
case HeaderSectionType.flashSale:
final products = _viewModel.flashSaleData.value?.products ?? [];
if (products.isNotEmpty) {
sections.add(
FlashSaleCarouselWidget(
products: products,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.flashSale),
onTap: (product) {
Get.toNamed(voucherDetailScreen, arguments: product.id);
},
),
);
}
break;
case HeaderSectionType.brand:
if (_viewModel.brands.isNotEmpty) {
sections.add(
BrandGridWidget(
brands: _viewModel.brands,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.brand),
onTap: (item) {
// Get.toNamed(affiliateDetailScreen, arguments: item.brandId);
},
),
);
}
break;
case HeaderSectionType.pointPartner:
if (_viewModel.affiliates.isNotEmpty) {
sections.add(
AffiliateBrandGridWidget(
affiliateBrands: _viewModel.affiliates,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.pointPartner),
onTap: (item) {
// Get.toNamed(affiliateDetailScreen, arguments: item.brandId);
},
),
);
}
break;
default:
break;
}
final sections = _viewModel.sectionLayouts
.map(_buildSection)
.whereType<Widget>()
.toList();
if (sections.isEmpty) {
return const [Padding(padding: EdgeInsets.symmetric(vertical: 40), child: EmptyWidget())];
}
return sections;
}
Widget? _buildSection(MainSectionConfigModel section) {
switch (section.headerSectionType) {
case HeaderSectionType.banner:
if (_viewModel.banners.isEmpty) return null;
return BannerCarousel(
banners: _viewModel.banners,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.banner),
onTap: (item) => item.directionalScreen?.begin(),
);
case HeaderSectionType.topButton:
if (_viewModel.services.isEmpty) return null;
return MainServiceGrid(
services: _viewModel.services,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.topButton),
onTap: (item) => item.directionalScreen?.begin(),
);
case HeaderSectionType.campaign:
if (_viewModel.achievements.isEmpty) return null;
return AchievementCarousel(
items: _viewModel.achievements,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.campaign),
onTap: (item) => item.directionScreen?.begin(),
);
case HeaderSectionType.product:
if (_viewModel.products.isEmpty) return null;
final displayProducts = List<ProductModel>.from(_viewModel.products);
if (displayProducts.length.isOdd) {
displayProducts.removeLast();
}
if (displayProducts.isEmpty) return null;
return ProductGrid(
products: displayProducts,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.product),
onTap: (product) => Get.toNamed(voucherDetailScreen, arguments: product.id),
);
case HeaderSectionType.news:
if (_viewModel.news.isEmpty) return null;
return NewsCarouselWidget(
items: _viewModel.news,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.news),
onTap: (item) => Get.toNamed(campaignDetailScreen, arguments: {"id": item.pageId}),
);
case HeaderSectionType.myProduct:
if (_viewModel.myProducts.isEmpty) return null;
return MyProductCarouselWidget(
items: _viewModel.myProducts,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.myProduct),
onTap: (item) => Get.toNamed(voucherDetailScreen, arguments: {"customerProductId": item.id}),
);
case HeaderSectionType.flashSale:
final products = _viewModel.flashSaleData.value?.products ?? [];
if (products.isEmpty) return null;
return FlashSaleCarouselWidget(
products: products,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.flashSale),
onTap: (product) => Get.toNamed(voucherDetailScreen, arguments: product.id),
);
case HeaderSectionType.brand:
if (_viewModel.brands.isEmpty) return null;
return BrandGridWidget(
brands: _viewModel.brands,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.brand),
onTap: (_) {},
);
case HeaderSectionType.pointPartner:
if (_viewModel.affiliates.isEmpty) return null;
return AffiliateBrandGridWidget(
affiliateBrands: _viewModel.affiliates,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.pointPartner),
onTap: (_) {},
);
default:
return null;
}
}
@override
Widget build(BuildContext context) {
final paddingBottom = MediaQuery.of(context).padding.bottom + 20;
......@@ -193,14 +152,14 @@ class _HomeScreenState extends State<HomeScreen> with PopupOnInit {
body: Stack(
children: [
NestedScrollView(
physics: AlwaysScrollableScrollPhysics(),
physics: const AlwaysScrollableScrollPhysics(),
headerSliverBuilder: (_, _) => [_buildSliverHeader(heightHeader)],
body: RefreshIndicator(
onRefresh: _onRefresh,
child: Obx(() {
return ListView(
padding: EdgeInsets.only(bottom: paddingBottom),
physics: AlwaysScrollableScrollPhysics(),
physics: const AlwaysScrollableScrollPhysics(),
children: _buildSectionContent(),
);
}),
......
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
......@@ -29,9 +30,22 @@ class HomeTabViewModel extends RestfulApiViewModel {
final Rxn<FlashSaleModel> flashSaleData = Rxn<FlashSaleModel>();
final Rxn<HoverDataModel> hoverData = Rxn<HoverDataModel>();
late final Map<HeaderSectionType, Future<void> Function(MainSectionConfigModel)> _sectionLoaders;
@override
void onInit() {
super.onInit();
_sectionLoaders = {
HeaderSectionType.topButton: _loadTopButtons,
HeaderSectionType.banner: _loadBanners,
HeaderSectionType.campaign: _loadCampaigns,
HeaderSectionType.product: _loadProducts,
HeaderSectionType.news: _loadNews,
HeaderSectionType.flashSale: _loadFlashSale,
HeaderSectionType.brand: _loadBrands,
HeaderSectionType.pointPartner: _loadAffiliateBrands,
HeaderSectionType.myProduct: _loadMyProducts,
};
getSectionLayoutHome();
loadDataPiPiHome();
}
......@@ -43,20 +57,9 @@ class HomeTabViewModel extends RestfulApiViewModel {
Future<void> getSectionLayoutHome({bool showLoading = true}) async {
await callApi<List<MainSectionConfigModel>>(
request: () => client.getSectionLayoutHome(),
onSuccess: (data, _) {
sectionLayouts.assignAll(data);
},
onFailure: (_, _, _) async {
sectionLayouts.assignAll(await _loadSectionLayoutHomeFromCache());
},
onComplete: () async {
if (sectionLayouts.isEmpty) {
sectionLayouts.assignAll(await _loadSectionLayoutHomeFromCache());
}
for (final section in sectionLayouts) {
await _processSection(section);
}
},
onSuccess: (data, _) => _resolveSectionLayouts(data),
onFailure: (message, response, error) async =>
_resolveSectionLayouts(await _loadSectionLayoutHomeFromCache()),
withLoading: showLoading,
);
}
......@@ -74,78 +77,97 @@ class HomeTabViewModel extends RestfulApiViewModel {
Future<List<MainSectionConfigModel>> _loadSectionLayoutHomeFromCache() async {
final jsonStr = await rootBundle.loadString('assets/data/main_layout_section_home.json');
final List<dynamic> jsonList = json.decode(jsonStr);
return jsonList.map((e) => MainSectionConfigModel.fromJson(e)).toList() ?? [];
}
Future<void> _processSection(MainSectionConfigModel section) async {
final path = section.apiList ?? "";
switch (section.headerSectionType) {
case HeaderSectionType.topButton:
final res = await client.fetchList<MainServiceModel>(
path,
(json) => MainServiceModel.fromJson(json as Map<String, dynamic>),
);
services.assignAll(res.data ?? []);
break;
case HeaderSectionType.banner:
final res = await client.fetchList<BannerModel>(
path,
(json) => BannerModel.fromJson(json as Map<String, dynamic>),
);
banners.assignAll(res.data ?? []);
break;
case HeaderSectionType.campaign:
final res = await client.fetchList<AchievementModel>(
path,
(json) => AchievementModel.fromJson(json as Map<String, dynamic>),
);
achievements.assignAll(res.data ?? []);
break;
case HeaderSectionType.product:
final res = await client.fetchList<ProductModel>(
path,
(json) => ProductModel.fromJson(json as Map<String, dynamic>),
);
products.assignAll(res.data ?? []);
break;
case HeaderSectionType.news:
final res = await client.fetchList<PageItemModel>(
path,
(json) => PageItemModel.fromJson(json as Map<String, dynamic>),
);
news.assignAll(res.data ?? []);
break;
case HeaderSectionType.flashSale:
final res = await client.fetchObject<FlashSaleModel>(
path,
(json) => FlashSaleModel.fromJson(json as Map<String, dynamic>),
);
flashSaleData.value = res.data;
break;
case HeaderSectionType.brand:
final res = await client.fetchList<BrandModel>(
path,
(json) => BrandModel.fromJson(json as Map<String, dynamic>),
);
brands.assignAll(res.data ?? []);
break;
case HeaderSectionType.pointPartner:
final res = await client.fetchList<AffiliateBrandModel>(
path,
(json) => AffiliateBrandModel.fromJson(json as Map<String, dynamic>),
);
affiliates.assignAll((res.data ?? []).take(6).toList());
break;
case HeaderSectionType.myProduct:
final res = await client.fetchList<MyProductModel>(
path,
(json) => MyProductModel.fromJson(json as Map<String, dynamic>),
);
myProducts.assignAll(res.data ?? []);
break;
default:
print("Unknown section type: ${section.headerSectionType}");
break;
return jsonList.map((e) => MainSectionConfigModel.fromJson(e)).toList();
}
Future<void> _resolveSectionLayouts(List<MainSectionConfigModel> layouts) async {
final resolved = layouts.isEmpty ? await _loadSectionLayoutHomeFromCache() : layouts;
sectionLayouts.assignAll(resolved);
await _processSections(resolved);
}
Future<void> _processSections(List<MainSectionConfigModel> sections) async {
final futures = <Future<void>>[];
for (final section in sections) {
final loader = _sectionLoaders[section.headerSectionType];
if (loader != null) {
futures.add(loader(section));
} else {
debugPrint('HomeTabViewModel: Unsupported section type ${section.headerSectionType}');
}
}
await Future.wait(futures);
}
Future<void> _loadTopButtons(MainSectionConfigModel section) async {
final res = await client.fetchList<MainServiceModel>(
section.apiList ?? '',
(json) => MainServiceModel.fromJson(json as Map<String, dynamic>),
);
services.assignAll(res.data ?? []);
}
Future<void> _loadBanners(MainSectionConfigModel section) async {
final res = await client.fetchList<BannerModel>(
section.apiList ?? '',
(json) => BannerModel.fromJson(json as Map<String, dynamic>),
);
banners.assignAll(res.data ?? []);
}
Future<void> _loadCampaigns(MainSectionConfigModel section) async {
final res = await client.fetchList<AchievementModel>(
section.apiList ?? '',
(json) => AchievementModel.fromJson(json as Map<String, dynamic>),
);
achievements.assignAll(res.data ?? []);
}
Future<void> _loadProducts(MainSectionConfigModel section) async {
final res = await client.fetchList<ProductModel>(
section.apiList ?? '',
(json) => ProductModel.fromJson(json as Map<String, dynamic>),
);
products.assignAll(res.data ?? []);
}
Future<void> _loadNews(MainSectionConfigModel section) async {
final res = await client.fetchList<PageItemModel>(
section.apiList ?? '',
(json) => PageItemModel.fromJson(json as Map<String, dynamic>),
);
news.assignAll(res.data ?? []);
}
Future<void> _loadFlashSale(MainSectionConfigModel section) async {
final res = await client.fetchObject<FlashSaleModel>(
section.apiList ?? '',
(json) => FlashSaleModel.fromJson(json as Map<String, dynamic>),
);
flashSaleData.value = res.data;
}
Future<void> _loadBrands(MainSectionConfigModel section) async {
final res = await client.fetchList<BrandModel>(
section.apiList ?? '',
(json) => BrandModel.fromJson(json as Map<String, dynamic>),
);
brands.assignAll(res.data ?? []);
}
Future<void> _loadAffiliateBrands(MainSectionConfigModel section) async {
final res = await client.fetchList<AffiliateBrandModel>(
section.apiList ?? '',
(json) => AffiliateBrandModel.fromJson(json as Map<String, dynamic>),
);
affiliates.assignAll((res.data ?? []).take(6).toList());
}
Future<void> _loadMyProducts(MainSectionConfigModel section) async {
final res = await client.fetchList<MyProductModel>(
section.apiList ?? '',
(json) => MyProductModel.fromJson(json as Map<String, dynamic>),
);
myProducts.assignAll(res.data ?? []);
}
}
\ No newline at end of file
}
......@@ -30,13 +30,12 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
@override
void initState() {
super.initState();
ever(_viewModel.errorMessage, (value) {
if (value != null && value.toString().isNotEmpty) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showAlertError(content: value);
});
}
});
_viewModel.onShowAlertError = (message) {
if (message.isEmpty) return;
WidgetsBinding.instance.addPostFrameCallback((_) {
showAlertError(content: message);
});
};
DetailPageRuleType? type;
String? pageId;
final args = Get.arguments;
......@@ -52,7 +51,7 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
return Scaffold(
backgroundColor: BaseColor.second200,
body: Obx(() {
CampaignDetailModel? pageDetail = _viewModel.campaignDetail.value.data?.pageDetail;
CampaignDetailModel? pageDetail = _viewModel.campaignDetail.value?.pageDetail;
if (pageDetail == null) {
return Stack(
children: [
......
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/configs/constants.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 'model/campaign_detail_model.dart';
import 'model/detail_page_rule_type.dart';
import '../faqs/faqs_model.dart';
class CampaignDetailViewModel extends RestfulApiViewModel {
var campaignDetail = BaseResponseModel<CampaignDetailResponseModel>().obs;
var isLoading = false.obs;
var errorMessage = "".obs;
var campaignDetail = Rxn<CampaignDetailResponseModel>();
void Function(String message)? onShowAlertError;
void fetchData(DetailPageRuleType? type, String? pageId) {
Future<void> fetchData(DetailPageRuleType? type, String? pageId) async {
if ((pageId ?? "").isNotEmpty) {
fetchWebsitePageGetDetail(pageId!);
await fetchWebsitePageGetDetail(pageId!);
return;
}
if (type != null) {
fetchWebsitePage(type!);
await fetchWebsitePage(type);
return;
}
fetchFAQItems();
await fetchFAQItems();
}
Future<void> fetchFAQItems() async {
showLoading();
isLoading(true);
client.websiteFolderGetPageList({"folder_uri": "ABOUT"}).then((value) {
hideLoading();
isLoading(false);
final pageId = (value.data?.items ?? []).first.pageId ?? "";
if (pageId.isEmpty) {
errorMessage.value = Constants.commonError;
} else {
fetchWebsitePageGetDetail(pageId);
}
});
await callApi<FAQItemModelResponse>(
request: () => client.websiteFolderGetPageList({"folder_uri": "ABOUT"}),
onSuccess: (data, _) async {
final pageId = (data.items ?? [])
.firstWhereOrNull((item) => (item.pageId ?? "").isNotEmpty)
?.pageId ??
"";
if (pageId.isEmpty) {
onShowAlertError?.call(Constants.commonError);
return;
}
await fetchWebsitePageGetDetail(pageId);
},
onFailure: (msg, _, _) async {
onShowAlertError?.call(msg);
},
);
}
void fetchWebsitePage(DetailPageRuleType type) {
showLoading();
isLoading(true);
client.websitePage(type).then((value) {
campaignDetail.value = value;
if (!value.isSuccess) {
errorMessage.value = value.errorMessage ?? Constants.commonError;
}
hideLoading();
isLoading(false);
});
Future<void> fetchWebsitePage(DetailPageRuleType type) async {
await callApi<CampaignDetailResponseModel>(
request: () => client.websitePage(type),
onSuccess: (data, _) {
campaignDetail.value = data;
},
onFailure: (msg, _, _) async {
onShowAlertError?.call(msg);
},
);
}
void fetchWebsitePageGetDetail(String pageId) {
showLoading();
isLoading(true);
client.websitePageGetDetail(pageId).then((value) {
campaignDetail.value = value;
if (!value.isSuccess) {
errorMessage.value = value.errorMessage ?? Constants.commonError;
}
hideLoading();
isLoading(false);
});
Future<void> fetchWebsitePageGetDetail(String pageId) async {
await callApi<CampaignDetailResponseModel>(
request: () => client.websitePageGetDetail(pageId),
onSuccess: (data, response) {
campaignDetail.value = data;
},
onFailure: (msg, response, error) async {
onShowAlertError?.call(msg);
},
);
}
Future<void> submitShareContent() async {
final path = campaignDetail.value.data?.pageDetail?.clickButtonShare ?? "";
final path = campaignDetail.value?.pageDetail?.clickButtonShare ?? "";
if (path.isEmpty) return;
await client.submitShareContent(path);
}
......
......@@ -4,6 +4,7 @@ import 'package:get/get.dart';
import 'package:mypoint_flutter_app/directional/directional_screen.dart';
import 'package:mypoint_flutter_app/extensions/num_extension.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import 'package:mypoint_flutter_app/preference/point/point_manager.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../directional/directional_action_type.dart';
......@@ -26,24 +27,17 @@ class PersonalScreen extends BaseScreen {
class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, PopupOnInit {
final _headerHomeVM = Get.find<HeaderHomeViewModel>();
String? _version, _buildNumber;
final _pointManager = UserPointManager();
late final Future<List<String?>> _appInfoFuture;
@override
void initState() {
super.initState();
_loadAppInfo();
_appInfoFuture = Future.wait([AppInfoHelper.version, AppInfoHelper.buildNumber]);
_pointManager.fetchUserPoint();
runPopupCheck(DirectionalScreenName.personal);
}
Future<void> _loadAppInfo() async {
final v = await AppInfoHelper.version;
final b = await AppInfoHelper.buildNumber;
setState(() {
_version = v ?? "";
_buildNumber = b ?? "";
});
}
@override
Widget createBody() {
return Scaffold(
......@@ -56,7 +50,17 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po
children: [
_buildInvitationSection(),
_buildMenuItems(),
_buildVersionInfo(_version, _buildNumber),
FutureBuilder<List<String?>> (
future: _appInfoFuture,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox.shrink();
}
final version = snapshot.data?[0] ?? '';
final buildNumber = snapshot.data?[1] ?? '';
return _buildVersionInfo(version, buildNumber);
},
),
Container(color: Colors.grey[200], height: MediaQuery.of(context).padding.bottom + 16),
],
),
......@@ -145,10 +149,12 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po
},
child: Row(
children: [
Text(
(data.totalPointActive ?? 0).money(CurrencyUnit.point),
style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold),
),
Obx(() {
return Text(
(_pointManager.pointStream.value).money(CurrencyUnit.point),
style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold),
);
}),
const SizedBox(width: 4),
const Icon(Icons.chevron_right, color: Colors.white, size: 22),
],
......@@ -346,7 +352,6 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po
return;
}
print("Safe back to login screen with phone: $phone, displayName: $displayName");
if (phone.isNotEmpty) {
await DataPreference.instance.clearLoginToken();
Get.offAllNamed(loginScreen, arguments: {"phone": phone, 'fullName': displayName});
......
......@@ -18,6 +18,8 @@ class CheckUpdateResponseModel {
switch (updateMode?.toUpperCase() ?? "") {
case 'NOW':
return UpdateStatus.force;
case 'NONE':
return UpdateStatus.none;
default:
return UpdateStatus.suggest;
}
......@@ -34,4 +36,3 @@ class CheckUpdateResponseModel {
Map<String, dynamic> toJson() => _$CheckUpdateResponseModelToJson(this);
}
......@@ -2,6 +2,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import 'package:mypoint_flutter_app/screen/splash/splash_screen_viewmodel.dart';
import 'package:mypoint_flutter_app/shared/router_gage.dart';
import 'package:mypoint_flutter_app/widgets/alert/custom_alert_dialog.dart';
......@@ -33,7 +34,7 @@ class _SplashScreenState extends BaseState<SplashScreen> with BasicState {
}
var status = updateData?.status ?? UpdateStatus.none;
if (status == UpdateStatus.none) {
Get.offAllNamed(onboardingScreen);
_viewModel.directionWhenTokenInvalid();
} else {
_showSuggestUpdateAlert(updateData);
}
......
......@@ -50,7 +50,7 @@ class SplashScreenViewModel extends RestfulApiViewModel {
final token = await _getTokenFromSDK();
if (token.orEmpty.isEmpty) {
_directionWhenTokenInvalid();
directionWhenTokenInvalid();
return;
}
......@@ -59,7 +59,7 @@ class SplashScreenViewModel extends RestfulApiViewModel {
} on Object catch (e) {
debugPrint('❌ SplashScreen - makeDataFollowInitApp error: $e');
DataPreference.instance.clearLoginToken();
_directionWhenTokenInvalid();
directionWhenTokenInvalid();
}
}
......@@ -96,25 +96,30 @@ class SplashScreenViewModel extends RestfulApiViewModel {
final profile = await _fetchUserProfile();
if (profile == null) {
DataPreference.instance.clearLoginToken();
_directionWhenTokenInvalid();
directionWhenTokenInvalid();
return;
}
await _prepareInitialData(profile);
_goToMain();
}
void _directionWhenTokenInvalid() {
Future<void> directionWhenTokenInvalid() async {
if (Get.currentRoute == onboardingScreen) return;
Get.offAllNamed(onboardingScreen);
// if (kIsWeb) {
// print('❌ No token found on web, closing app');
// webCloseApp({
// 'message': 'No token found, cannot proceed',
// 'timestamp': DateTime.now().millisecondsSinceEpoch,
// });
// } else {
// Get.toNamed(onboardingScreen);
// }
if (kIsWeb) {
print('❌ No token found on web, closing app');
final closeSuccess = await webCloseApp({
'message': 'No token found, cannot proceed',
'timestamp': DateTime.now().millisecondsSinceEpoch,
});
if (closeSuccess) return;
}
final phone = DataPreference.instance.phoneNumberUsedForLoginScreen;
final displayName = DataPreference.instance.displayName;
if (phone.isEmpty) {
Get.offAllNamed(onboardingScreen);
} else {
Get.offAllNamed(loginScreen, arguments: {"phone": phone, 'fullName': displayName});
}
}
Future<ProfileResponseModel?> _fetchUserProfile() async {
......
......@@ -39,7 +39,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
@override
void initState() {
super.initState();
UserPointManager().fetchUserPoint();
int? productId;
int? customerProductId;
......@@ -482,9 +482,12 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
});
}
_handleRedeemProduct() {
Future<void> _handleRedeemProduct() async {
final point = await UserPointManager().fetchUserPoint(withLoading: true) ?? 0;
final amountToBePaid = _viewModel.product.value?.amountToBePaid ?? 0;
if (UserPointManager().point < amountToBePaid) {
print('amountToBePaid: $amountToBePaid');
print('UserPointManager().point: $point');
if (point < amountToBePaid) {
showAlertError(content: "Bạn không đủ điểm để đổi ưu đãi này");
return;
}
......
......@@ -99,7 +99,7 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
});
}
redeemProduct() {
void redeemProduct() {
showLoading();
final requestId = Uuid().v4();
client
......
......@@ -63,7 +63,7 @@ class VoucherListItem extends StatelessWidget {
child: loadNetworkImage(
url: bgImage,
fit: BoxFit.cover,
placeholderAsset: 'assets/images/sample.png',
placeholderAsset: 'assets/images/bg_default_169.png',
),
),
if (!product.inStock)
......
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