Commit 75178f29 authored by DatHV's avatar DatHV
Browse files

update notify screen,

voucher QR code
parent 33ec1dde
...@@ -50,4 +50,6 @@ class APIPaths { ...@@ -50,4 +50,6 @@ class APIPaths {
static const String deleteNotification = "/notificationDeleteOne/1.0.0"; static const String deleteNotification = "/notificationDeleteOne/1.0.0";
static const String deleteAllNotifications = "/notificationDeleteAll/1.0.0"; static const String deleteAllNotifications = "/notificationDeleteAll/1.0.0";
static const String notificationMarkAsSeen = "/notificationMarkAsSeen/1.0.0"; static const String notificationMarkAsSeen = "/notificationMarkAsSeen/1.0.0";
static const String notificationGetDetail = "/notificationGetDetail/1.0.0";
static const String getCustomerProductDetail = "/product/api/v2.0/customer/products/%@";
} }
\ No newline at end of file
enum ClickActionType { import '../shared/router_gage.dart';
campaignDetail,
enum DirectionalScreenName {
flashSale,
searchProduct,
setting,
cashBackPointPartnerDetail,
notifications,
notification,
appScreen,
website,
voucher,
productOwnVoucher,
myMobileCard,
brand,
transaction,
link,
viewDeepLink,
viewDeepLinkInApp,
viewGift,
feedback,
dailyCheckin,
dailyCheckinScreen,
inputReferralCode,
myPurchaseItems,
myPurchaseItemsWaitingList,
achievement,
productVoucherLike,
productMobileCard,
workerProfile,
customerInviteFriend,
customerTransferPoint,
customerReviewApp,
preferentialHotList,
pointHistory,
refundHistory,
home,
pointBack,
brandList,
news,
brandLike,
productVoucher,
memberShip,
ranking,
mobileTopup,
mobileTopupData,
topup,
register,
gifts,
viewSMS,
walkingCampaign,
applicationSetting,
customerSupport,
historyInvitedFriend,
gameWorldCup2022,
rateStorePopup,
simService,
vietlott,
shoppingOnline,
electricBill,
partnerRedirect,
brandOffline,
pipiScreen,
campaignSevenDayScreen,
surveyCampaign,
inviteFriend,
viewVoucherWithCountTime,
viewAllVoucher,
popViewController,
finishScreen,
screenAddInvitationCode,
luckyMoney,
termPolicyDeleteAccount,
privacyPolicy,
termsOfUse,
termPolicyDecree13,
bankAccountManager,
familyMedon,
familyMedonDetailCard,
familyHealthBook,
gameCenter,
vnTraPackage,
myVnTraPackage,
detailTrafficService,
makeDirectionScreen,
webviewFullScreen,
gamesBundle,
gameCardDetail,
newInviteFriend,
inviteFriendApply,
personal,
voucherTab,
introduction,
listPaymentOfElectric,
favorite,
unknown,
} }
extension ClickActionTypeExtension on ClickActionType { // const splashScreen = '/splash';
String get key { // const onboardingScreen = '/onboarding';
// const loginScreen = '/login';
// const mainScreen = '/main';
// const vouchersScreen = '/vouchers';
// const gameCardScreen = '/gameCardScreen';
// const registerFormInputScreen = '/registerFormInputScreen';
// const transactionDetailScreen = '/transactionDetailScreen';
// const baseWebViewScreen = '/baseWebViewScreen';
// const paymentWebViewScreen = '/paymentWebViewScreen';
// const transactionHistoryDetailScreen = '/transactionHistoryDetailScreen';
extension DirectionalScreenRouterExtension on DirectionalScreenName {
String get router {
switch (this) { switch (this) {
case ClickActionType.campaignDetail: case DirectionalScreenName.setting:
return "campaignDetail"; return settingScreen;
case DirectionalScreenName.notifications:
return notificationScreen;
case DirectionalScreenName.home:
return mainScreen;
case DirectionalScreenName.productOwnVoucher:
return voucherDetailScreen;
case DirectionalScreenName.customerSupport:
return supportScreen;
default:
return '';
} }
} }
}
static ClickActionType? fromString(String value) { extension DirectionalScreenNameExtension on DirectionalScreenName {
switch (value) { String get rawValue {
case "campaignDetail": switch (this) {
return ClickActionType.campaignDetail; case DirectionalScreenName.flashSale:
default: return "APP_SCREEN_FLASH_SALE";
return null; case DirectionalScreenName.searchProduct:
return "APP_SCREEN_SEARCH_PRODUCTS";
case DirectionalScreenName.setting:
return "APP_SCREEN_SETTING";
case DirectionalScreenName.cashBackPointPartnerDetail:
return "APP_SCREEN_CASHBACK_PARTNER_DETAIL";
case DirectionalScreenName.notifications:
return "APP_SCREEN_NOTIFICATIONS";
case DirectionalScreenName.notification:
return "NOTIFICATION_DETAIL";
case DirectionalScreenName.appScreen:
return "VIEW_APP_SCREEN";
case DirectionalScreenName.website:
return "VIEW_WEBSITE_PAGE";
case DirectionalScreenName.voucher:
return "VIEW_PRODUCT_VOUCHER";
case DirectionalScreenName.productOwnVoucher:
return "VIEW_PRODUCT_OWN_VOUCHER";
case DirectionalScreenName.myMobileCard:
return "VIEW_MY_MOBILE_CARD";
case DirectionalScreenName.brand:
return "VIEW_BRAND";
case DirectionalScreenName.transaction:
return "VIEW_TRANSACTION_REQUEST";
case DirectionalScreenName.link:
return "VIEW_LINK";
case DirectionalScreenName.viewDeepLink:
return "VIEW_DEEP_LINK";
case DirectionalScreenName.viewDeepLinkInApp:
return "VIEW_DEEP_LINK_INAPP";
case DirectionalScreenName.viewGift:
return "VIEW_GIFT";
case DirectionalScreenName.feedback:
return "FEEDBACK";
case DirectionalScreenName.dailyCheckin:
return "DAILY_CHECKIN";
case DirectionalScreenName.dailyCheckinScreen:
return "APP_SCREEN_CUSTOMER_DAILY_CHECKIN";
case DirectionalScreenName.inputReferralCode:
return "INPUT_REFERRAL_CODE";
case DirectionalScreenName.myPurchaseItems:
return "APP_SCREEN_MY_PURCHASE_ITEMS";
case DirectionalScreenName.myPurchaseItemsWaitingList:
return "APP_SCREEN_MY_PURCHASE_ITEMS-WAITING_LIST";
case DirectionalScreenName.achievement:
return "APP_SCREEN_ACHIEVEMENT";
case DirectionalScreenName.productVoucherLike:
return "APP_SCREEN_PRODUCT_VOUCHER_LIKES";
case DirectionalScreenName.productMobileCard:
return "APP_SCREEN_PRODUCT_MOBILE_CARD";
case DirectionalScreenName.workerProfile:
return "APP_SCREEN_WORKER_PROFILE";
case DirectionalScreenName.customerInviteFriend:
return "APP_SCREEN_CUSTOMER_INVITE_FRIEND";
case DirectionalScreenName.customerTransferPoint:
return "APP_SCREEN_CUSTOMER_TRANSFERT_POINT";
case DirectionalScreenName.customerReviewApp:
return "APP_SCREEN_CUSTOMER_REVIEW_APP";
case DirectionalScreenName.preferentialHotList:
return "APP_SCREEN_PREFERENTIAL_HOT_LIST";
case DirectionalScreenName.pointHistory:
return "APP_SCREEN_POINT_HISTORY";
case DirectionalScreenName.refundHistory:
return "APP_SCREEN_REFUND_HISTORY";
case DirectionalScreenName.home:
return "APP_SCREEN_HOME";
case DirectionalScreenName.pointBack:
return "APP_SCREEN_POINTBACK";
case DirectionalScreenName.brandList:
return "APP_SCREEN_BRAND_LIST";
case DirectionalScreenName.news:
return "APP_SCREEN_NEWS";
case DirectionalScreenName.brandLike:
return "APP_SCREEN_BRAND_LIKES";
case DirectionalScreenName.productVoucher:
return "APP_SCREEN_PRODUCT_VOUCHER";
case DirectionalScreenName.memberShip:
return "APP_SCREEN_MEMBERSHIP_LEVEL";
case DirectionalScreenName.ranking:
return "APP_SCREEN_RANKING_PROGRAM";
case DirectionalScreenName.mobileTopup:
return "APP_SCREEN_PRODUCT_MOBILE_TOPUP";
case DirectionalScreenName.mobileTopupData:
return "APP_SCREEN_MOBILE_TOPUP_DATA";
case DirectionalScreenName.topup:
return "APP_SCREEN_TOPUP";
case DirectionalScreenName.register:
return "APP_SCREEN_REGISTER";
case DirectionalScreenName.gifts:
return "APP_SCREEN_GIFTS";
case DirectionalScreenName.viewSMS:
return "VIEW_SMS";
case DirectionalScreenName.walkingCampaign:
return "APP_SCREEN_CAMPAIGN_WALKING";
case DirectionalScreenName.applicationSetting:
return "APPLICATION_SETTING";
case DirectionalScreenName.customerSupport:
return "APP_SCREEN_CUSTOMER_FEEDBACK";
case DirectionalScreenName.historyInvitedFriend:
return "APP_SCREEN_HISTORY_INVITED";
case DirectionalScreenName.gameWorldCup2022:
return "APP_SCREEN_GAME_WORLDCUP";
case DirectionalScreenName.rateStorePopup:
return "OPEN_RATE_STORE";
case DirectionalScreenName.simService:
return "APP_SCREEN_SIM_SERVICE";
case DirectionalScreenName.vietlott:
return "APP_SCREEN_VIETLOTT";
case DirectionalScreenName.shoppingOnline:
return "APP_SCREEN_SHOPPING_ONLINE";
case DirectionalScreenName.electricBill:
return "APP_SCREEN_ELECTRIC_BILL";
case DirectionalScreenName.partnerRedirect:
return "PARTNER_REDIRECT";
case DirectionalScreenName.brandOffline:
return "VIEW_BRAND_TAB_VOUCHER_OFFLINE";
case DirectionalScreenName.pipiScreen:
return "APP_SCREEN_PIPI";
case DirectionalScreenName.campaignSevenDayScreen:
return "APP_SCREEN_CAMPAIGN_TASKS";
case DirectionalScreenName.surveyCampaign:
return "APP_SCREEN_SURVERY_APP";
case DirectionalScreenName.inviteFriend:
return "APP_SCREEN_INVITE_FRIEND";
case DirectionalScreenName.viewVoucherWithCountTime:
return "APP_SCREEN_VIEW_VOUCHER";
case DirectionalScreenName.viewAllVoucher:
return "APP_SCREEN_ALL_VOUCHER";
case DirectionalScreenName.popViewController:
return "POP_VIEW_CONTROLLER";
case DirectionalScreenName.finishScreen:
return "FINISHED_SCREEN";
case DirectionalScreenName.screenAddInvitationCode:
return "APP_SCREEN_ADD_INVITATION_CODE";
case DirectionalScreenName.luckyMoney:
return "APP_SCREEN_LUCKY_MONEY";
case DirectionalScreenName.termPolicyDeleteAccount:
return "TERM_POLICY_DELETE_ACCOUNT";
case DirectionalScreenName.privacyPolicy:
return "APP_SCREEN_PRIVACY_POLICY";
case DirectionalScreenName.termsOfUse:
return "APP_SCREEN_TERMS_OF_USE";
case DirectionalScreenName.termPolicyDecree13:
return "TERM_POLICY_DECREE_13";
case DirectionalScreenName.bankAccountManager:
return "BANK_ACCOUNT_MANAGER";
case DirectionalScreenName.familyMedon:
return "APP_SCREEN_CARD_FAMILY_MEDON";
case DirectionalScreenName.familyMedonDetailCard:
return "MEDON_DETAIL_CARD";
case DirectionalScreenName.familyHealthBook:
return "FAMILY_HEALTH_BOOK";
case DirectionalScreenName.gameCenter:
return "VPLAY_GAME_CENTER";
case DirectionalScreenName.vnTraPackage:
return "APP_SCREEN_VNTRA_PACKAGE";
case DirectionalScreenName.myVnTraPackage:
return "APP_SCREEN_MY_VNTRA_PACKAGE";
case DirectionalScreenName.detailTrafficService:
return "DETAIL_TRAFFIC_SERVICES";
case DirectionalScreenName.makeDirectionScreen:
return "GET_DIRECTION_SCREEN";
case DirectionalScreenName.webviewFullScreen:
return "APP_VIEW_WEBVIEW_FULL_SCREEN";
case DirectionalScreenName.gamesBundle:
return "APP_SCREEN_GAME_BUNDLE";
case DirectionalScreenName.gameCardDetail:
return "APP_SCREEN_GAME_CARD_DETAIL";
case DirectionalScreenName.newInviteFriend:
return "APP_SCREEN_INVITE_FRIEND_MANAGER";
case DirectionalScreenName.inviteFriendApply:
return "APP_SCREEN_INVITE_FRIEND_APPLY";
case DirectionalScreenName.personal:
return "APP_SCREEN_PERSONAL";
case DirectionalScreenName.voucherTab:
return "APP_SCREEN_VOUCHER_TAB";
case DirectionalScreenName.introduction:
return "APP_SCREEN_INTRODUCTION";
case DirectionalScreenName.listPaymentOfElectric:
return "APP_SCREEN_LIST_PAYMENT_OF_ELECTRIC";
case DirectionalScreenName.favorite:
return "APP_SCREEN_CATEGORY_TAB_FAVORITE";
case DirectionalScreenName.unknown:
return "UNKNOWN";
} }
} }
}
\ No newline at end of file static DirectionalScreenName fromRawValue(String rawValue) {
return DirectionalScreenName.values.firstWhere(
(e) => e.rawValue == rawValue,
orElse: () => DirectionalScreenName.unknown,
);
}
static fromString(String s) {}
}
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../screen/pageDetail/campaign_detail_screen.dart'; import '../screen/pageDetail/campaign_detail_screen.dart';
import '../shared/router_gage.dart';
import 'directional_action_type.dart'; import 'directional_action_type.dart';
part 'directional_screen.g.dart'; part 'directional_screen.g.dart';
@JsonSerializable() @JsonSerializable()
class DirectionalScreen { class DirectionalScreen {
@JsonKey(name: "click_action_type") @JsonKey(name: "click_action_type")
final String? clickActionType; final String? clickActionType;
@JsonKey(name: "click_action_param") @JsonKey(name: "click_action_param")
final String? clickActionParam; final String? clickActionParam;
final ClickActionType? actionType;
DirectionalScreen({ DirectionalScreen({this.clickActionType, this.clickActionParam});
this.clickActionType,
this.clickActionParam, static DirectionalScreen? build({String? clickActionType, String? clickActionParam}) {
this.actionType, if (clickActionType == null || clickActionType.isEmpty) return null;
}); return DirectionalScreen(clickActionType: clickActionType, clickActionParam: clickActionParam);
}
factory DirectionalScreen.fromJson(Map<String, dynamic> json) => _$DirectionalScreenFromJson(json); factory DirectionalScreen.fromJson(Map<String, dynamic> json) => _$DirectionalScreenFromJson(json);
Map<String, dynamic> toJson() => _$DirectionalScreenToJson(this); Map<String, dynamic> toJson() => _$DirectionalScreenToJson(this);
void begin() { @immutable
final type = ClickActionTypeExtension.fromString(clickActionType ?? actionType?.key ?? ""); bool begin() {
final type = DirectionalScreenNameExtension.fromRawValue(clickActionType ?? "");
if (type == null) { if (type == null) {
print("Không nhận diện được action type: $clickActionType"); print("Không nhận diện được action type: $clickActionType");
return; return false;
} }
switch (type) { switch (type) {
case ClickActionType.campaignDetail: case DirectionalScreenName.setting:
Get.to(() => const CampaignDetailScreen(), arguments: clickActionParam); Get.toNamed(settingScreen);
break; return true;
case DirectionalScreenName.productOwnVoucher:
Get.toNamed(voucherDetailScreen, arguments: {"customerProductId": int.parse(clickActionParam ?? "")});
return true;
default:
print("Không nhận diện được action type: $clickActionType");
return false;
} }
} }
} }
\ No newline at end of file
...@@ -10,19 +10,10 @@ DirectionalScreen _$DirectionalScreenFromJson(Map<String, dynamic> json) => ...@@ -10,19 +10,10 @@ DirectionalScreen _$DirectionalScreenFromJson(Map<String, dynamic> json) =>
DirectionalScreen( DirectionalScreen(
clickActionType: json['click_action_type'] as String?, clickActionType: json['click_action_type'] as String?,
clickActionParam: json['click_action_param'] as String?, clickActionParam: json['click_action_param'] as String?,
actionType: $enumDecodeNullable(
_$ClickActionTypeEnumMap,
json['actionType'],
),
); );
Map<String, dynamic> _$DirectionalScreenToJson(DirectionalScreen instance) => Map<String, dynamic> _$DirectionalScreenToJson(DirectionalScreen instance) =>
<String, dynamic>{ <String, dynamic>{
'click_action_type': instance.clickActionType, 'click_action_type': instance.clickActionType,
'click_action_param': instance.clickActionParam, 'click_action_param': instance.clickActionParam,
'actionType': _$ClickActionTypeEnumMap[instance.actionType],
}; };
const _$ClickActionTypeEnumMap = {
ClickActionType.campaignDetail: 'campaignDetail',
};
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:mypoint_flutter_app/model/auth/customer_balance_model.dart'; import 'package:mypoint_flutter_app/model/auth/customer_balance_model.dart';
import 'customer_balance_model.dart';
part 'working_site_model.g.dart'; part 'working_site_model.g.dart';
@JsonSerializable() @JsonSerializable()
class WorkingSiteModel { class WorkingSiteModel {
final String? id; final String? id;
final String? name; final String? name;
final String? avatar; final String? avatar;
@JsonKey(name: 'customer_balance')
final CustomerBalanceModel? customerBalanceModel;
@JsonKey(name: 'primary_membership')
// final PrimaryMembership? primaryMembership;
WorkingSiteModel({ WorkingSiteModel({
this.id, this.id,
this.name, this.name,
this.avatar, this.avatar,
this.customerBalanceModel,
// this.primaryMembership,
}); });
factory WorkingSiteModel.fromJson(Map<String, dynamic> json) => _$WorkingSiteModelFromJson(json); factory WorkingSiteModel.fromJson(Map<String, dynamic> json) => _$WorkingSiteModelFromJson(json);
......
...@@ -11,12 +11,6 @@ WorkingSiteModel _$WorkingSiteModelFromJson(Map<String, dynamic> json) => ...@@ -11,12 +11,6 @@ WorkingSiteModel _$WorkingSiteModelFromJson(Map<String, dynamic> json) =>
id: json['id'] as String?, id: json['id'] as String?,
name: json['name'] as String?, name: json['name'] as String?,
avatar: json['avatar'] as String?, avatar: json['avatar'] as String?,
customerBalanceModel:
json['customer_balance'] == null
? null
: CustomerBalanceModel.fromJson(
json['customer_balance'] as Map<String, dynamic>,
),
); );
Map<String, dynamic> _$WorkingSiteModelToJson(WorkingSiteModel instance) => Map<String, dynamic> _$WorkingSiteModelToJson(WorkingSiteModel instance) =>
...@@ -24,5 +18,4 @@ Map<String, dynamic> _$WorkingSiteModelToJson(WorkingSiteModel instance) => ...@@ -24,5 +18,4 @@ Map<String, dynamic> _$WorkingSiteModelToJson(WorkingSiteModel instance) =>
'id': instance.id, 'id': instance.id,
'name': instance.name, 'name': instance.name,
'avatar': instance.avatar, 'avatar': instance.avatar,
'customer_balance': instance.customerBalanceModel,
}; };
...@@ -16,6 +16,7 @@ import '../preference/point/header_home_model.dart'; ...@@ -16,6 +16,7 @@ import '../preference/point/header_home_model.dart';
import '../screen/faqs/faqs_model.dart'; import '../screen/faqs/faqs_model.dart';
import '../screen/game/models/game_bundle_item_model.dart'; import '../screen/game/models/game_bundle_item_model.dart';
import '../screen/notification/models/category_notify_item_model.dart'; import '../screen/notification/models/category_notify_item_model.dart';
import '../screen/notification/models/notification_detail_model.dart';
import '../screen/notification/models/notification_list_data_model.dart'; import '../screen/notification/models/notification_list_data_model.dart';
import '../screen/onboarding/model/check_phone_response_model.dart'; import '../screen/onboarding/model/check_phone_response_model.dart';
import '../screen/onboarding/model/onboarding_info_model.dart'; import '../screen/onboarding/model/onboarding_info_model.dart';
...@@ -284,6 +285,11 @@ extension RestfullAPIClientAllApi on RestfulAPIClient { ...@@ -284,6 +285,11 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
return requestNormal(path, Method.GET, {}, (data) => ProductModel.fromJson(data as Json)); return requestNormal(path, Method.GET, {}, (data) => ProductModel.fromJson(data as Json));
} }
Future<BaseResponseModel<ProductModel>> getCustomerProductDetail(int id) async {
final path = APIPaths.getCustomerProductDetail.replaceAll("%@", id.toString());
return requestNormal(path, Method.GET, {}, (data) => ProductModel.fromJson(data as Json));
}
Future<BaseResponseModel<List<ProductStoreModel>>> getProductStores(int id) async { Future<BaseResponseModel<List<ProductStoreModel>>> getProductStores(int id) async {
final body = {"product_id": id, "size": 20, "index": 0}; final body = {"product_id": id, "size": 20, "index": 0};
return requestNormal(APIPaths.getProductStores, Method.GET, body, (data) { return requestNormal(APIPaths.getProductStores, Method.GET, body, (data) {
...@@ -398,7 +404,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient { ...@@ -398,7 +404,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
} }
Future<BaseResponseModel<EmptyCodable>> deleteNotification(String id) async { Future<BaseResponseModel<EmptyCodable>> deleteNotification(String id) async {
return requestNormal(APIPaths.deleteNotification, Method.POST, {"notification_id": id}, (data) { return requestNormal(APIPaths.deleteNotification, Method.POST, {"notification_id__": id}, (data) {
return EmptyCodable.fromJson(data as Json); return EmptyCodable.fromJson(data as Json);
}); });
} }
...@@ -419,6 +425,14 @@ extension RestfullAPIClientAllApi on RestfulAPIClient { ...@@ -419,6 +425,14 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
}); });
} }
Future<BaseResponseModel<NotificationDetailResponseModel>> getNotificationDetail(String id) async {
String? token = DataPreference.instance.token ?? "";
final body = {"notification_id": id, "mark_as_seen": "1", "access_token": token};
return requestNormal(APIPaths.notificationGetDetail, Method.POST, body, (data) {
return NotificationDetailResponseModel.fromJson(data as Json);
});
}
Future<BaseResponseModel<TransactionHistoryModel>> getTransactionHistoryDetail(String id) async { Future<BaseResponseModel<TransactionHistoryModel>> getTransactionHistoryDetail(String id) async {
final path = APIPaths.getTransactionHistoryDetail.replaceAll("%@", id); final path = APIPaths.getTransactionHistoryDetail.replaceAll("%@", id);
return requestNormal(path, Method.GET, {}, (data) { return requestNormal(path, Method.GET, {}, (data) {
......
import 'package:json_annotation/json_annotation.dart';
import 'package:mypoint_flutter_app/directional/directional_screen.dart';
import '../../../model/auth/working_site_model.dart';
part 'notification_detail_model.g.dart';
@JsonSerializable()
class NotificationDetailModel {
@JsonKey(name: 'notification_id')
final String? notificationId;
final String? title;
final String? body;
final String? type;
@JsonKey(name: 'click_action_type')
final String? clickActionType;
@JsonKey(name: 'click_action_param')
final String? clickActionParam;
@JsonKey(name: 'create_time')
final String? createTime;
@JsonKey(name: 'seen_at')
final String? seenAt;
@JsonKey(name: 'working_site')
final WorkingSiteModel? workingSite;
DirectionalScreen? get directionalScreen {
return DirectionalScreen.build(
clickActionType: clickActionType,
clickActionParam: clickActionParam,
);
}
NotificationDetailModel({
this.notificationId,
this.title,
this.body,
this.type,
this.clickActionType,
this.clickActionParam,
this.createTime,
this.seenAt,
this.workingSite,
});
factory NotificationDetailModel.fromJson(Map<String, dynamic> json) =>
_$NotificationDetailModelFromJson(json);
Map<String, dynamic> toJson() => _$NotificationDetailModelToJson(this);
}
@JsonSerializable()
class NotificationDetailResponseModel {
final NotificationDetailModel? notification;
NotificationDetailResponseModel({this.notification});
factory NotificationDetailResponseModel.fromJson(Map<String, dynamic> json) =>
_$NotificationDetailResponseModelFromJson(json);
Map<String, dynamic> toJson() => _$NotificationDetailResponseModelToJson(this);
}
\ No newline at end of file
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'notification_detail_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
NotificationDetailModel _$NotificationDetailModelFromJson(
Map<String, dynamic> json,
) => NotificationDetailModel(
notificationId: json['notification_id'] as String?,
title: json['title'] as String?,
body: json['body'] as String?,
type: json['type'] as String?,
clickActionType: json['click_action_type'] as String?,
clickActionParam: json['click_action_param'] as String?,
createTime: json['create_time'] as String?,
seenAt: json['seen_at'] as String?,
workingSite:
json['working_site'] == null
? null
: WorkingSiteModel.fromJson(
json['working_site'] as Map<String, dynamic>,
),
);
Map<String, dynamic> _$NotificationDetailModelToJson(
NotificationDetailModel instance,
) => <String, dynamic>{
'notification_id': instance.notificationId,
'title': instance.title,
'body': instance.body,
'type': instance.type,
'click_action_type': instance.clickActionType,
'click_action_param': instance.clickActionParam,
'create_time': instance.createTime,
'seen_at': instance.seenAt,
'working_site': instance.workingSite,
};
NotificationDetailResponseModel _$NotificationDetailResponseModelFromJson(
Map<String, dynamic> json,
) => NotificationDetailResponseModel(
notification:
json['notification'] == null
? null
: NotificationDetailModel.fromJson(
json['notification'] as Map<String, dynamic>,
),
);
Map<String, dynamic> _$NotificationDetailResponseModelToJson(
NotificationDetailResponseModel instance,
) => <String, dynamic>{'notification': instance.notification};
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../../directional/directional_screen.dart';
part 'notification_item_model.g.dart'; part 'notification_item_model.g.dart';
@JsonSerializable() @JsonSerializable()
class NotificationItemModel { class NotificationItemModel {
...@@ -22,6 +24,13 @@ class NotificationItemModel { ...@@ -22,6 +24,13 @@ class NotificationItemModel {
bool get hasSeen => (seenAt ?? "").isNotEmpty; bool get hasSeen => (seenAt ?? "").isNotEmpty;
DirectionalScreen? get directionalScreen {
return DirectionalScreen.build(
clickActionType: clickActionType,
clickActionParam: clickActionParam,
);
}
NotificationItemModel({ NotificationItemModel({
this.notificationId, this.notificationId,
this.title, this.title,
......
import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/widgets/custom_app_bar.dart';
import '../../widgets/image_loader.dart';
import 'models/notification_detail_model.dart';
class NotificationDetailScreen extends StatelessWidget {
final NotificationDetailModel notification;
const NotificationDetailScreen({super.key, required this.notification});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar.back(title: "Chi tiết thông báo"),
body: Container(
color: Colors.grey.shade100,
padding: const EdgeInsets.all(16),
child: IntrinsicHeight(
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: loadNetworkImage(
url: notification.workingSite?.avatar ?? "",
fit: BoxFit.cover,
width: 40,
height: 40,
placeholderAsset: 'assets/images/ic_logo.png',
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(notification.title ?? '', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
const SizedBox(height: 6),
Text(notification.body ?? '', style: const TextStyle(fontSize: 14)),
const SizedBox(height: 10),
Text(_timeAgo(notification.createTime), style: const TextStyle(color: Colors.grey, fontSize: 13)),
],
),
),
],
),
),
),
),
);
}
String _timeAgo(String? dateStr) {
if (dateStr == null) return '';
final time = DateTime.tryParse(dateStr);
if (time == null) return '';
final now = DateTime.now();
final diff = now.difference(time);
if (diff.inDays > 0) return '${diff.inDays} ngày trước';
if (diff.inHours > 0) return '${diff.inHours} giờ trước';
if (diff.inMinutes > 0) return '${diff.inMinutes} phút trước';
return 'Vừa xong';
}
}
...@@ -34,6 +34,11 @@ class _NotificationScreenState extends BaseState<NotificationScreen> with BasicS ...@@ -34,6 +34,11 @@ class _NotificationScreenState extends BaseState<NotificationScreen> with BasicS
_viewModel.fetchNotifications(refresh: false); _viewModel.fetchNotifications(refresh: false);
} }
}); });
_viewModel.onShowAlertError = (message) {
if (message.isNotEmpty) {
showAlertError(content: message);
}
};
} }
@override @override
...@@ -166,11 +171,11 @@ class _NotificationScreenState extends BaseState<NotificationScreen> with BasicS ...@@ -166,11 +171,11 @@ class _NotificationScreenState extends BaseState<NotificationScreen> with BasicS
_confirmDeleteAllNotifications() { _confirmDeleteAllNotifications() {
final dataAlert = DataAlertModel( final dataAlert = DataAlertModel(
title: "Quên mật khẩu", title: "Xoá tất cả",
description: "Bạn cần đăng xuất khỏi tài khoản này để đặt lại mật khẩu. Bạn chắc chứ?.", description: "Bạn có muốn xoá hết tất cả thông báo không? \nLưu ý: bạn sẽ không thể xem lại thông báo đã xoá",
localHeaderImage: "assets/images/ic_pipi_03.png", localHeaderImage: "assets/images/ic_pipi_03.png",
buttons: [AlertButton( buttons: [AlertButton(
text: "Đồng ý", text: "Xoá",
onPressed: () { onPressed: () {
Get.back(); Get.back();
_viewModel.deleteAllNotifications(); _viewModel.deleteAllNotifications();
...@@ -258,45 +263,66 @@ class _NotificationScreenState extends BaseState<NotificationScreen> with BasicS ...@@ -258,45 +263,66 @@ class _NotificationScreenState extends BaseState<NotificationScreen> with BasicS
} }
Widget _buildNotificationItem(NotificationItemModel item) { Widget _buildNotificationItem(NotificationItemModel item) {
return Container( return Dismissible(
margin: const EdgeInsets.only(bottom: 12), key: ValueKey(item.notificationId ?? item.createTime),
padding: const EdgeInsets.all(12), direction: DismissDirection.endToStart,
decoration: BoxDecoration( background: Container(
color: item.hasSeen ? Colors.white : Colors.pink.shade50, alignment: Alignment.centerRight,
borderRadius: BorderRadius.circular(12) padding: const EdgeInsets.symmetric(horizontal: 20),
color: Colors.red,
child: const Icon(Icons.delete, color: Colors.white),
), ),
child: Column( onDismissed: (direction) {
crossAxisAlignment: CrossAxisAlignment.start, _viewModel.handleRemoveNotification(item);
children: [ // _viewModel.notifications.remove(item);
Row( },
child: GestureDetector(
onTap: () {
print("Click item ${item.title}");
_viewModel.handleClickNotification(item);
// item.directionalScreen?.begin();
},
child: Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: item.hasSeen ? Colors.white : Colors.pink.shade50,
borderRadius: BorderRadius.circular(12)
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ClipRRect( Row(
borderRadius: BorderRadius.circular(8), crossAxisAlignment: CrossAxisAlignment.start,
child: loadNetworkImage( children: [
url: item.workingSite?.avatar ?? "", ClipRRect(
fit: BoxFit.cover, borderRadius: BorderRadius.circular(8),
width: 40, child: loadNetworkImage(
height: 40, url: item.workingSite?.avatar ?? "",
placeholderAsset: 'assets/images/ic_logo.png', fit: BoxFit.cover,
), width: 40,
), height: 40,
const SizedBox(width: 12), placeholderAsset: 'assets/images/ic_logo.png',
Expanded( ),
child: Column( ),
crossAxisAlignment: CrossAxisAlignment.start, const SizedBox(width: 12),
children: [ Expanded(
Text(item.title ?? '', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15)), child: Column(
const SizedBox(height: 8), crossAxisAlignment: CrossAxisAlignment.start,
Text(item.body ?? '', style: const TextStyle(fontSize: 14)), children: [
const SizedBox(height: 8), Text(item.title ?? '', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15)),
Text(item.createTime ?? '', style: const TextStyle(fontSize: 13, color: Colors.black54)), const SizedBox(height: 8),
], Text(item.body ?? '', style: const TextStyle(fontSize: 14)),
), const SizedBox(height: 8),
Text(item.createTime ?? '', style: const TextStyle(fontSize: 13, color: Colors.black54)),
],
),
),
],
), ),
], ],
), ),
], ),
), ),
); );
} }
......
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/configs/constants.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart'; import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import '../../base/restful_api_viewmodel.dart'; import '../../base/restful_api_viewmodel.dart';
import '../../preference/data_preference.dart'; import '../../preference/data_preference.dart';
import 'models/category_notify_item_model.dart'; import 'models/category_notify_item_model.dart';
import 'models/notification_item_model.dart'; import 'models/notification_item_model.dart';
import 'notification_detail_screen.dart';
class NotificationViewModel extends RestfulApiViewModel { class NotificationViewModel extends RestfulApiViewModel {
var categories = RxList<CategoryNotifyItemModel>(); var categories = RxList<CategoryNotifyItemModel>();
var notifications = RxList<NotificationItemModel>(); var notifications = RxList<NotificationItemModel>();
final RxBool isLoading = false.obs; final RxBool isLoading = false.obs;
final _pageLimit = 10;
var _notificationIndex = 0;
void Function(String message)? onShowAlertError;
var _hasMoreData = true;
CategoryNotifyItemModel? get selectedCategory => CategoryNotifyItemModel? get selectedCategory =>
categories.isNotEmpty ? categories.firstWhere((item) => item.isSelected ?? false) : null; categories.isNotEmpty ? categories.firstWhere((item) => item.isSelected ?? false) : null;
...@@ -33,22 +39,29 @@ class NotificationViewModel extends RestfulApiViewModel { ...@@ -33,22 +39,29 @@ class NotificationViewModel extends RestfulApiViewModel {
void fetchNotifications({bool refresh = false}) async { void fetchNotifications({bool refresh = false}) async {
if (isLoading.value) return; if (isLoading.value) return;
if (!refresh && !_hasMoreData) { return; }
if (refresh) {
_notificationIndex = 0;
}
isLoading.value = true; isLoading.value = true;
String? token = DataPreference.instance.token ?? ""; String? token = DataPreference.instance.token ?? "";
final body = { final body = {
"access_token": token, "access_token": token,
"start": refresh ? 0 : notifications.length, "start": _notificationIndex,
"limit": 10, "limit": _pageLimit,
"noti_group_id": selectedCategory?.id ?? "", "noti_group_id": selectedCategory?.id ?? "",
}; };
client.getNotifications(body).then((value) { client.getNotifications(body).then((value) {
isLoading.value = false; isLoading.value = false;
hideLoading(); hideLoading();
final items = value.data?.items ?? [];
if (refresh) { if (refresh) {
notifications.value = value.data?.items ?? []; notifications.value = items;
} else { } else {
notifications.addAll(value.data?.items ?? []); notifications.addAll(items);
} }
_notificationIndex += items.length;
_hasMoreData = (value.data?.items?.length ?? 0) == _pageLimit;
}); });
} }
...@@ -70,4 +83,28 @@ class NotificationViewModel extends RestfulApiViewModel { ...@@ -70,4 +83,28 @@ class NotificationViewModel extends RestfulApiViewModel {
_fetchCategories(); _fetchCategories();
}); });
} }
handleRemoveNotification(NotificationItemModel item) {
if (item.notificationId == null) { return; }
client.deleteNotification(item.notificationId ?? "").then((value) {
notifications.remove(item);
if (notifications.length <= _pageLimit) {
fetchNotifications(refresh: false);
}
});
}
handleClickNotification(NotificationItemModel item) {
showLoading();
client.getNotificationDetail(item.notificationId ?? "").then((value) {
hideLoading();
if (!value.isSuccess) return;
final notification = value.data?.notification;
if (notification == null) return;
final pushSuccess = notification.directionalScreen?.begin() ?? false;
if (!pushSuccess) {
Get.to(() => NotificationDetailScreen(notification: notification));
}
});
}
} }
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
import 'package:mypoint_flutter_app/shared/direction_google_map.dart'; import 'package:mypoint_flutter_app/shared/direction_google_map.dart';
import '../models/product_store_model.dart'; import '../models/product_store_model.dart';
...@@ -11,29 +12,31 @@ class StoreListSection extends StatelessWidget { ...@@ -11,29 +12,31 @@ class StoreListSection extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (stores.isEmpty) { return Obx(() {
return const SizedBox.shrink(); if (stores.isEmpty) {
} return const SizedBox.shrink();
}
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('Địa điểm áp dụng:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), const Text('Địa điểm áp dụng:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
const SizedBox(height: 10), const SizedBox(height: 10),
// ...stores.map((store) => _buildStoreItem(store)).toList(), ...stores.map(
...stores.map((store) => InkWell( (store) => InkWell(
onTap: () { onTap: () {
_onTapStore(store); _onTapStore(store);
}, },
child: _buildStoreItem(store), child: _buildStoreItem(store),
)), ),
], ),
), ],
); ),
);
});
} }
_onTapStore(ProductStoreModel store) { _onTapStore(ProductStoreModel store) {
......
...@@ -4,6 +4,7 @@ import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; ...@@ -4,6 +4,7 @@ import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:mypoint_flutter_app/extensions/num_extension.dart'; import 'package:mypoint_flutter_app/extensions/num_extension.dart';
import 'package:mypoint_flutter_app/screen/voucher/detail/store_list_section.dart'; import 'package:mypoint_flutter_app/screen/voucher/detail/store_list_section.dart';
import 'package:mypoint_flutter_app/screen/voucher/models/product_type.dart'; import 'package:mypoint_flutter_app/screen/voucher/models/product_type.dart';
import 'package:mypoint_flutter_app/screen/voucher/voucher_code_card_screen.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../../../base/base_screen.dart'; import '../../../base/base_screen.dart';
import '../../../base/basic_state.dart'; import '../../../base/basic_state.dart';
...@@ -31,20 +32,31 @@ class VoucherDetailScreen extends BaseScreen { ...@@ -31,20 +32,31 @@ class VoucherDetailScreen extends BaseScreen {
} }
class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with BasicState { class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with BasicState {
late final int productId;
late final VoucherDetailViewModel _viewModel; late final VoucherDetailViewModel _viewModel;
double _infoHeight = 0; double _infoHeight = 0;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
int? productId;
int? customerProductId;
final args = Get.arguments; final args = Get.arguments;
if (args is int) { if (args is Map) {
productId = args;
} else if (args is Map) {
productId = args['productId']; productId = args['productId'];
customerProductId = args['customerProductId'];
}
if (productId == null && customerProductId == null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Get.back();
});
return;
} }
_viewModel = Get.put(VoucherDetailViewModel(productId: productId)); _viewModel = Get.put(VoucherDetailViewModel(
productId: productId,
customerProductId: customerProductId,
));
_viewModel.onShowAlertError = (message) { _viewModel.onShowAlertError = (message) {
if (message.isNotEmpty) { if (message.isNotEmpty) {
showAlertError(content: message); showAlertError(content: message);
...@@ -207,7 +219,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi ...@@ -207,7 +219,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
product.expired ? "Hết hạn" : product.expire, product.expired ? "Hết hạn" : product.expire,
style: const TextStyle(color: BaseColor.primary500, fontWeight: FontWeight.bold, fontSize: 12), style: const TextStyle(color: BaseColor.primary500, fontWeight: FontWeight.bold, fontSize: 12),
), ),
if (isOutOfStock) if (isOutOfStock && !_viewModel.isMyProduct)
Container( Container(
margin: const EdgeInsets.only(left: 8), margin: const EdgeInsets.only(left: 8),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
...@@ -315,9 +327,6 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi ...@@ -315,9 +327,6 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
Widget _buildBottomAction(ProductModel product) { Widget _buildBottomAction(ProductModel product) {
final bool isOutOfStock = !(product.inStock ?? true); final bool isOutOfStock = !(product.inStock ?? true);
if (isOutOfStock) {
return const SizedBox.shrink();
}
if (product.isMyProduct && product.customerInfoModel?.status != MyProductStatusType.waiting) { if (product.isMyProduct && product.customerInfoModel?.status != MyProductStatusType.waiting) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
...@@ -326,6 +335,8 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi ...@@ -326,6 +335,8 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
} }
if (product.isMyProduct) { if (product.isMyProduct) {
return _buildUseButton(); return _buildUseButton();
} else if (isOutOfStock) {
return const SizedBox.shrink();
} else if (product.price?.method == CashType.point) { } else if (product.price?.method == CashType.point) {
return _buildExchangeButton(); return _buildExchangeButton();
} else { } else {
...@@ -351,7 +362,8 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi ...@@ -351,7 +362,8 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
height: 48, height: 48,
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
// TODO: Handle sử dụng voucher if (_viewModel.product.value == null) return;
Get.to(() => VoucherCodeCardScreen(product: _viewModel.product.value!,));
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.green, backgroundColor: Colors.green,
...@@ -462,7 +474,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi ...@@ -462,7 +474,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
if (product?.price?.method == CashType.point) { if (product?.price?.method == CashType.point) {
_handleRedeemProduct(); _handleRedeemProduct();
} else { } else {
Get.toNamed(transactionDetailScreen, arguments: {"product": product, "quantity": _viewModel.quantity.value}); Get.toNamed(transactionDetailScreen, arguments: {"product": product, "quantity": _viewModel.quantity.value});
} }
}); });
} }
...@@ -475,23 +487,21 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi ...@@ -475,23 +487,21 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
} }
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 <b style=\"color:#E71D28\"> ${amountToBePaid.money(CurrencyUnit.point)}</b> MyPoint để đổi Ưu Đãi này không?", description:
"Bạn có muốn sử dụng <b style=\"color:#E71D28\"> ${amountToBePaid.money(CurrencyUnit.point)}</b> MyPoint để đổi Ưu Đãi này không?",
localHeaderImage: "assets/images/ic_pipi_02.png", localHeaderImage: "assets/images/ic_pipi_02.png",
buttons: [AlertButton( buttons: [
text: "Đồng ý",
onPressed: () {
Get.back();
_viewModel.redeemProduct();
},
bgColor: BaseColor.primary500,
textColor: Colors.white,
),
AlertButton( AlertButton(
text: "Huỷ", text: "Đồng ý",
onPressed: () => Get.back(), onPressed: () {
bgColor: Colors.white, Get.back();
textColor: BaseColor.second500, _viewModel.redeemProduct();
),], },
bgColor: BaseColor.primary500,
textColor: Colors.white,
),
AlertButton(text: "Huỷ", onPressed: () => Get.back(), bgColor: Colors.white, textColor: BaseColor.second500),
],
); );
showAlert(data: dataAlert); showAlert(data: dataAlert);
} }
......
...@@ -10,14 +10,21 @@ import '../models/product_model.dart'; ...@@ -10,14 +10,21 @@ import '../models/product_model.dart';
import '../models/product_store_model.dart'; import '../models/product_store_model.dart';
class VoucherDetailViewModel extends RestfulApiViewModel { class VoucherDetailViewModel extends RestfulApiViewModel {
final int productId; final int? productId;
VoucherDetailViewModel({required this.productId}); final int? customerProductId;
VoucherDetailViewModel({
this.productId,
this.customerProductId,
});
var stores = RxList<ProductStoreModel>(); var stores = RxList<ProductStoreModel>();
var product = Rxn<ProductModel>(); var product = Rxn<ProductModel>();
var isLoading = false.obs; var isLoading = false.obs;
var liked = false.obs; var liked = false.obs;
void Function(String message)? onShowAlertError; void Function(String message)? onShowAlertError;
var quantity = 1.obs; var quantity = 1.obs;
bool get isMyProduct => customerProductId != null;
int? get _id => productId ?? product.value?.id;
@override @override
void onInit() { void onInit() {
...@@ -27,6 +34,7 @@ class VoucherDetailViewModel extends RestfulApiViewModel { ...@@ -27,6 +34,7 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
} }
Future<void> toggleFavorite() async { Future<void> toggleFavorite() async {
if (_id == null) return;
final value = product.value; final value = product.value;
if (value == null) return; if (value == null) return;
try { try {
...@@ -35,7 +43,7 @@ class VoucherDetailViewModel extends RestfulApiViewModel { ...@@ -35,7 +43,7 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
value?.likeId = 0; value?.likeId = 0;
liked.value = false; liked.value = false;
} else { } else {
final response = await client.likeProduct(productId); final response = await client.likeProduct(_id!);
value?.likeId = response.data?.id; value?.likeId = response.data?.id;
liked.value = (response.data?.id ?? 0) != 0; liked.value = (response.data?.id ?? 0) != 0;
} }
...@@ -48,20 +56,25 @@ class VoucherDetailViewModel extends RestfulApiViewModel { ...@@ -48,20 +56,25 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
if (isLoading.value) return; if (isLoading.value) return;
try { try {
isLoading.value = true; isLoading.value = true;
final response = await client.getProduct(productId); final response = isMyProduct ? await client.getCustomerProductDetail(customerProductId ?? 0) : await client.getProduct(productId ?? 0);
product.value = response.data; product.value = response.data;
liked.value = product.value?.liked == true; liked.value = product.value?.liked == true;
} catch (error) { } catch (error) {
onShowAlertError?.call("Error fetching product detail: $error"); onShowAlertError?.call("Error fetching product detail: $error");
} finally { } finally {
isLoading.value = false; isLoading.value = false;
if (isMyProduct) {
_getProductStores();
}
} }
} }
Future<void> _getProductStores() async { Future<void> _getProductStores() async {
if (_id == null) return;
try { try {
final response = await client.getProductStores(productId); final response = await client.getProductStores(_id!);
stores.value = response.data ?? []; stores.value = response.data ?? [];
stores.refresh();
} catch (error) { } catch (error) {
onShowAlertError?.call("Error product stores: $error"); onShowAlertError?.call("Error product stores: $error");
print("Error product stores: $error"); print("Error product stores: $error");
......
...@@ -33,7 +33,7 @@ class ProductModel { ...@@ -33,7 +33,7 @@ class ProductModel {
@JsonKey(name: 'customer_product_info') @JsonKey(name: 'customer_product_info')
final ProductCustomerInfoModel? customerInfoModel; final ProductCustomerInfoModel? customerInfoModel;
@JsonKey(name: 'product_item') @JsonKey(name: 'product_item')
final ProductItemModel? itemModel; final ProductItemModel? item;
@JsonKey(name: 'expire_time') @JsonKey(name: 'expire_time')
final String? expireTime; final String? expireTime;
@JsonKey(name: 'require_form_regis') @JsonKey(name: 'require_form_regis')
...@@ -51,7 +51,7 @@ class ProductModel { ...@@ -51,7 +51,7 @@ class ProductModel {
this.media, this.media,
this.previewFlashSale, this.previewFlashSale,
this.customerInfoModel, this.customerInfoModel,
this.itemModel, this.item,
this.expireTime, this.expireTime,
this.requireFormRegis, this.requireFormRegis,
this.type, this.type,
...@@ -66,7 +66,7 @@ class ProductModel { ...@@ -66,7 +66,7 @@ class ProductModel {
} }
String get expire { String get expire {
final ex = (isMyProduct ? itemModel?.expireTime : expireTime) ?? ""; final ex = (isMyProduct ? item?.expireTime : expireTime) ?? "";
return ex.toDate()?.toFormattedString() ?? ""; return ex.toDate()?.toFormattedString() ?? "";
} }
......
...@@ -46,7 +46,7 @@ ProductModel _$ProductModelFromJson(Map<String, dynamic> json) => ProductModel( ...@@ -46,7 +46,7 @@ ProductModel _$ProductModelFromJson(Map<String, dynamic> json) => ProductModel(
: ProductCustomerInfoModel.fromJson( : ProductCustomerInfoModel.fromJson(
json['customer_product_info'] as Map<String, dynamic>, json['customer_product_info'] as Map<String, dynamic>,
), ),
itemModel: item:
json['product_item'] == null json['product_item'] == null
? null ? null
: ProductItemModel.fromJson( : ProductItemModel.fromJson(
...@@ -69,7 +69,7 @@ Map<String, dynamic> _$ProductModelToJson(ProductModel instance) => ...@@ -69,7 +69,7 @@ Map<String, dynamic> _$ProductModelToJson(ProductModel instance) =>
'media': instance.media, 'media': instance.media,
'preview_campaign_flash_sale': instance.previewFlashSale, 'preview_campaign_flash_sale': instance.previewFlashSale,
'customer_product_info': instance.customerInfoModel, 'customer_product_info': instance.customerInfoModel,
'product_item': instance.itemModel, 'product_item': instance.item,
'expire_time': instance.expireTime, 'expire_time': instance.expireTime,
'require_form_regis': instance.requireFormRegis, 'require_form_regis': instance.requireFormRegis,
'type': instance.type, 'type': instance.type,
......
...@@ -27,7 +27,7 @@ class VoucherItemGrid extends StatelessWidget { ...@@ -27,7 +27,7 @@ class VoucherItemGrid extends StatelessWidget {
final product = items[index]; final product = items[index];
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
Get.toNamed(voucherDetailScreen, arguments: product.id); Get.toNamed(voucherDetailScreen, arguments: {"productId": product.id});
}, },
child: _VoucherGridItem( child: _VoucherGridItem(
product: product, product: product,
......
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