Commit c285d072 authored by DatHV's avatar DatHV
Browse files

update game center

parent 8d264762
......@@ -31,4 +31,5 @@ class APIPaths {
static const String getProductStores = "/product/api/v2.0/product/stores";
static const String productCustomerLikes = "/product/api/v2.0/customer/likes";
static const String productCustomerUnlikes = "/product/api/v2.0/customer/likes/%@";
static const String getGames = "/campaign/api/v3.0/games";
}
\ No newline at end of file
import 'package:json_annotation/json_annotation.dart';
import 'directional_screen.dart';
part 'button_config_model.g.dart';
@JsonSerializable()
class ButtonConfigModel {
final String? text;
final String? color;
@JsonKey(name: "click_action_type")
final String? clickActionType;
@JsonKey(name: "click_action_param")
final String? clickActionParam;
final bool? hiden;
ButtonConfigModel({
this.text,
this.color,
this.clickActionType,
this.clickActionParam,
this.hiden,
});
factory ButtonConfigModel.fromJson(Map<String, dynamic> json) => _$ButtonConfigModelFromJson(json);
Map<String, dynamic> toJson() => _$ButtonConfigModelToJson(this);
DirectionalScreen? get directionScreen {
if (clickActionType != null || clickActionParam != null) {
return DirectionalScreen(
clickActionType: clickActionType,
clickActionParam: clickActionParam,
);
}
return null;
}
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'button_config_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ButtonConfigModel _$ButtonConfigModelFromJson(Map<String, dynamic> json) =>
ButtonConfigModel(
text: json['text'] as String?,
color: json['color'] as String?,
clickActionType: json['click_action_type'] as String?,
clickActionParam: json['click_action_param'] as String?,
hiden: json['hiden'] as bool?,
);
Map<String, dynamic> _$ButtonConfigModelToJson(ButtonConfigModel instance) =>
<String, dynamic>{
'text': instance.text,
'color': instance.color,
'click_action_type': instance.clickActionType,
'click_action_param': instance.clickActionParam,
'hiden': instance.hiden,
};
......@@ -42,4 +42,14 @@ extension StringDateExtension on String {
return null;
}
}
DateTime? toDateFormat(String format) {
if (trim().isEmpty) return null;
try {
return intl.DateFormat(format).parseStrict(this);
} catch (e) {
print('❌ Date parse failed for "$this" with format "$format": $e');
return null;
}
}
}
......@@ -5,6 +5,7 @@ import 'package:mypoint_flutter_app/configs/constants.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart';
import 'package:mypoint_flutter_app/networking/restful_api.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import 'package:mypoint_flutter_app/screen/game/models/game_bundle_response.dart';
import 'package:mypoint_flutter_app/screen/voucher/models/product_model.dart';
import '../configs/device_info.dart';
import '../model/auth/biometric_register_response_model.dart';
......@@ -314,4 +315,10 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
(data) => EmptyCodable.fromJson(data as Json),
);
}
Future<BaseResponseModel<GameBundleResponse>> getGames() async {
return requestNormal(APIPaths.getGames, Method.GET, {}, (data) {
return GameBundleResponse.fromJson(data as Json);
});
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import '../../../widgets/back_button.dart';
import '../models/game_bundle_item_model.dart';
import '../models/game_card_item_model.dart';
class GameCardScreen extends StatefulWidget {
const GameCardScreen({super.key});
@override
State<GameCardScreen> createState() => _GameCardScreenState();
}
class _GameCardScreenState extends State<GameCardScreen> {
late final GameBundleItemModel data;
@override
void initState() {
super.initState();
final args = Get.arguments;
if (args is GameBundleItemModel) {
data = args;
}
}
@override
Widget build(BuildContext context) {
final cards = data.options ?? [];
final screenHeight = MediaQuery.of(context).size.height;
final startTop = screenHeight * 560 / 1920;
return Scaffold(
body: Stack(
children: [
// Background full màn
Container(
decoration: BoxDecoration(
image: data.background != null
? DecorationImage(image: NetworkImage(data.background!), fit: BoxFit.cover)
: null,
color: Colors.green[100],
),
),
// Button Back
SafeArea(
child: Padding(
padding: const EdgeInsets.all(8),
child: CustomBackButton(),
),
),
Positioned(
top: startTop,
left: 16,
right: 16,
bottom: 0,
child: GridView.builder(
physics: const NeverScrollableScrollPhysics(),
itemCount: cards.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 20,
crossAxisSpacing: 20,
childAspectRatio: 3 / 4,
),
itemBuilder: (context, index) {
final card = cards[index];
return GameCardItem(card: card);
},
),
),
],
),
);
}
}
class GameCardItem extends StatelessWidget {
final GameCardItemModel card;
const GameCardItem({super.key, required this.card});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
print(card.id);
},
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 4, offset: Offset(2, 2))],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child:
card.image != null
? ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.network(card.image!, fit: BoxFit.cover),
)
: const SizedBox(),
),
const SizedBox(height: 6),
],
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../shared/router_gage.dart';
import '../../widgets/custom_empty_widget.dart';
import '../../widgets/custom_navigation_bar.dart';
import 'game_tab_viewmodel.dart';
class GameTabScreen extends BaseScreen {
const GameTabScreen({super.key});
@override
State<GameTabScreen> createState() => _GameTabScreenState();
}
class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState {
final GameTabViewModel _viewModel = Get.put(GameTabViewModel());
final LayerLink _layerLink = LayerLink();
final GlobalKey _infoKey = GlobalKey();
OverlayEntry? _popupEntry;
bool _isPopupShown = false;
@override
void initState() {
super.initState();
_viewModel.getGames();
}
@override
Widget createBody() {
return Scaffold(
appBar: CustomNavigationBar(
title: "Games",
showBackButton: false,
rightButtons: [
CompositedTransformTarget(
link: _layerLink,
child: IconButton(
key: _infoKey,
icon: const Icon(Icons.info_outline, color: Colors.white),
onPressed: _togglePopup,
),
),
],
),
body: Obx(() {
if (_viewModel.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
if (_viewModel.games.isEmpty) {
return const Center(child: EmptyWidget());
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text(
_viewModel.turnsNumberText.value,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
Expanded(
child: ListView.separated(
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16),
itemCount: _viewModel.games.length,
separatorBuilder: (_, __) => const SizedBox(height: 4),
itemBuilder: (context, index) {
final item = _viewModel.games[index];
return GestureDetector(
onTap: () {
Get.toNamed(gameCardScreen, arguments: item);
},
child: AspectRatio(
aspectRatio: 343/132,
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.network(
item.icon ?? '',
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => const ColoredBox(
color: Colors.grey,
child: Center(child: Icon(Icons.broken_image)),
),
),
),
),
);
},
),
),
],
);
}),
);
}
void _togglePopup() {
if (_isPopupShown) {
_hidePopup();
} else {
_showPopup();
}
}
void _showPopup() {
final overlay = Overlay.of(context);
final renderBox = _infoKey.currentContext?.findRenderObject() as RenderBox?;
final offset = renderBox?.localToGlobal(Offset.zero) ?? Offset.zero;
final size = renderBox?.size ?? Size.zero;
final widthSize = MediaQuery.of(context).size.width * 0.85;
_popupEntry = OverlayEntry(
builder: (context) => Stack(
children: [
// 👉 Tap ngoài popup để close
Positioned.fill(
child: GestureDetector(
onTap: _hidePopup,
behavior: HitTestBehavior.translucent,
child: Container(color: Colors.transparent),
),
),
Positioned(
top: offset.dy + size.height + 8,
left: widthSize*0.15/0.85/2, // offset.dx - widthSize,
child: Material(
borderRadius: BorderRadius.circular(16),
elevation: 4,
child: Container(
width: widthSize,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: const Text(
'1/ Thể lể trò chơi rất đơn giản, khách hàng sử dụng dịch vụ gói cước viễn thông của chúng tôi sẽ có cơ hội tham gia chơi game, khách hàng nào chơi cũng có thưởng do MyPoint (PayTech) tài trợ 100% phần quà.'
'\n\n2/ Ngoài ra các giải thưởng và luật lệ trong trò chơi không phải do Apple quản lý và tài trợ, điều này đã được thể hiện trong thể lệ và văn bản công bố trò chơi với khách hàng.',
style: TextStyle(fontSize: 14),
),
),
),
),
],
),
);
overlay.insert(_popupEntry!);
_isPopupShown = true;
}
void _hidePopup() {
_popupEntry?.remove();
_popupEntry = null;
_isPopupShown = false;
}
}
\ No newline at end of file
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import 'package:mypoint_flutter_app/screen/game/models/game_bundle_item_model.dart';
import '../../base/restful_api_viewmodel.dart';
import '../../configs/constants.dart';
class GameTabViewModel extends RestfulApiViewModel {
final RxList<GameBundleItemModel> games = <GameBundleItemModel>[].obs;
var turnsNumberText = "".obs;
var isLoading = false.obs;
var errorMessage = "".obs;
void getGames() {
isLoading(true);
client.getGames().then((value) {
if (!value.isSuccess) {
errorMessage.value = value.errorMessage ?? Constants.commonError;
} else {
games.value = value.data?.games ?? [];
turnsNumberText.value = value.data?.turnsNumberText ?? "";
}
isLoading(false);
});
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
class GameScreen extends StatelessWidget {
const GameScreen({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: Text('Games'));
}
}
\ No newline at end of file
import 'package:json_annotation/json_annotation.dart';
import 'game_card_item_model.dart';
part 'game_bundle_item_model.g.dart';
@JsonSerializable()
class GameBundleItemModel {
final String? id;
final String? name;
final String? icon;
final String? background;
final String? description;
final List<GameCardItemModel>? options;
GameBundleItemModel({
this.id,
this.name,
this.icon,
this.background,
this.description,
this.options,
});
factory GameBundleItemModel.fromJson(Map<String, dynamic> json) => _$GameBundleItemModelFromJson(json);
Map<String, dynamic> toJson() => _$GameBundleItemModelToJson(this);
}
\ No newline at end of file
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'game_bundle_item_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
GameBundleItemModel _$GameBundleItemModelFromJson(Map<String, dynamic> json) =>
GameBundleItemModel(
id: json['id'] as String?,
name: json['name'] as String?,
icon: json['icon'] as String?,
background: json['background'] as String?,
description: json['description'] as String?,
options:
(json['options'] as List<dynamic>?)
?.map(
(e) => GameCardItemModel.fromJson(e as Map<String, dynamic>),
)
.toList(),
);
Map<String, dynamic> _$GameBundleItemModelToJson(
GameBundleItemModel instance,
) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'icon': instance.icon,
'background': instance.background,
'description': instance.description,
'options': instance.options,
};
import 'package:json_annotation/json_annotation.dart';
import 'game_bundle_item_model.dart';
part 'game_bundle_response.g.dart';
@JsonSerializable()
class GameBundleResponse {
@JsonKey(name: 'turns_number_text')
final String? turnsNumberText;
final List<GameBundleItemModel>? games;
GameBundleResponse({
this.turnsNumberText,
this.games,
});
factory GameBundleResponse.fromJson(Map<String, dynamic> json) => _$GameBundleResponseFromJson(json);
Map<String, dynamic> toJson() => _$GameBundleResponseToJson(this);
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'game_bundle_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
GameBundleResponse _$GameBundleResponseFromJson(Map<String, dynamic> json) =>
GameBundleResponse(
turnsNumberText: json['turns_number_text'] as String?,
games:
(json['games'] as List<dynamic>?)
?.map(
(e) => GameBundleItemModel.fromJson(e as Map<String, dynamic>),
)
.toList(),
);
Map<String, dynamic> _$GameBundleResponseToJson(GameBundleResponse instance) =>
<String, dynamic>{
'turns_number_text': instance.turnsNumberText,
'games': instance.games,
};
import 'package:json_annotation/json_annotation.dart';
part 'game_card_item_model.g.dart';
@JsonSerializable()
class GameCardItemModel {
final int? id;
final String? image;
final int? stt;
GameCardItemModel({
this.id,
this.image,
this.stt,
});
factory GameCardItemModel.fromJson(Map<String, dynamic> json) => _$GameCardItemModelFromJson(json);
Map<String, dynamic> toJson() => _$GameCardItemModelToJson(this);
}
\ No newline at end of file
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'game_card_item_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
GameCardItemModel _$GameCardItemModelFromJson(Map<String, dynamic> json) =>
GameCardItemModel(
id: (json['id'] as num?)?.toInt(),
image: json['image'] as String?,
stt: (json['stt'] as num?)?.toInt(),
);
Map<String, dynamic> _$GameCardItemModelToJson(GameCardItemModel instance) =>
<String, dynamic>{
'id': instance.id,
'image': instance.image,
'stt': instance.stt,
};
import 'package:flutter/material.dart';
import '../game/games_screen.dart';
import '../../resouce/base_color.dart';
import '../game/game_tab_screen.dart';
import '../home/home_screen.dart';
import '../personal/personal_screen.dart';
import '../shopping/shopping_screen.dart';
import '../support/transaction_history_screen.dart';
import '../voucher/voucher_tab_screen.dart';
......@@ -19,7 +19,7 @@ class _MainTabScreenState extends State<MainTabScreen> {
final List<Widget> _pages = const [
HomeScreen(),
VoucherTabScreen(),
GameScreen(),
GameTabScreen(),
TransactionHistoryScreen(),
PersonalScreen(),
];
......@@ -27,22 +27,27 @@ class _MainTabScreenState extends State<MainTabScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent, // cho sáng nền tổng thể
extendBody: true,
body: _pages[_currentIndex],
bottomNavigationBar: Container(
decoration: const BoxDecoration(
color: Colors.red,
),
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16),
child: SafeArea(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildTabItem(icon: Icons.home, label: 'Trang chủ', index: 0),
_buildTabItem(icon: Icons.star, label: 'Ưu đãi', index: 1),
_buildTabItem(icon: Icons.videogame_asset, label: 'Game', index: 2),
_buildTabItem(icon: Icons.shopping_cart, label: 'Mua sắm', index: 3),
_buildTabItem(icon: Icons.person, label: 'Cá nhân', index: 4),
],
bottomNavigationBar: SafeArea(
minimum: const EdgeInsets.only(left: 12, right: 12, bottom: 12),
child: ClipRRect(
borderRadius: BorderRadius.circular(132),
child: Container(
height: 72,
color: BaseColor.primary500,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildTabItem(icon: Icons.home, label: 'Trang chủ', index: 0),
_buildTabItem(icon: Icons.star, label: 'Ưu đãi', index: 1),
_buildTabItem(icon: Icons.videogame_asset, label: 'Game', index: 2),
_buildTabItem(icon: Icons.shopping_cart, label: 'Mua sắm', index: 3),
_buildTabItem(icon: Icons.person, label: 'Cá nhân', index: 4),
],
),
),
),
),
......@@ -51,18 +56,30 @@ class _MainTabScreenState extends State<MainTabScreen> {
Widget _buildTabItem({required IconData icon, required String label, required int index}) {
final isSelected = _currentIndex == index;
return GestureDetector(
onTap: () => setState(() => _currentIndex = index),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: Colors.white.withOpacity(isSelected ? 1 : 0.6)),
const SizedBox(height: 4),
Text(label, style: TextStyle(
color: Colors.white.withOpacity(isSelected ? 1 : 0.6),
fontSize: 12,
)),
],
return Expanded(
child: GestureDetector(
onTap: () => setState(() => _currentIndex = index),
child: Container(
color: Colors.transparent,
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon,
size: 28,
color: isSelected ? Colors.white : Colors.white70),
const SizedBox(height: 2),
Text(
label,
style: TextStyle(
color: isSelected ? Colors.white : Colors.white70,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
fontSize: 12,
),
),
],
),
),
),
);
}
......
......@@ -3,9 +3,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:mypoint_flutter_app/screen/voucher/detail/store_list_section.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../base/base_screen.dart';
import '../../../base/basic_state.dart';
import '../../../resouce/base_color.dart';
import '../../../widgets/back_button.dart';
import '../../../widgets/custom_empty_widget.dart';
import '../../../widgets/custom_point_text_tag.dart';
import '../../../widgets/custom_price_tag.dart';
import '../../../widgets/dashed_line.dart';
import '../../../widgets/image_loader.dart';
......@@ -16,14 +19,14 @@ import '../models/product_model.dart';
import 'voucher_detail_viewmodel.dart';
import 'package:get/get.dart';
class VoucherDetailScreen extends StatefulWidget {
class VoucherDetailScreen extends BaseScreen {
const VoucherDetailScreen({super.key});
@override
_VoucherDetailScreenState createState() => _VoucherDetailScreenState();
}
class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with BasicState {
late final int productId;
late final VoucherDetailViewModel _viewModel;
double _infoHeight = 0;
......@@ -39,10 +42,15 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
productId = args['productId'];
}
_viewModel = Get.put(VoucherDetailViewModel(productId: productId));
_viewModel.onShowAlertError = (message) {
if (message.isNotEmpty) {
showAlertError(content: message);
}
};
}
@override
Widget build(BuildContext context) {
Widget createBody() {
return Scaffold(
backgroundColor: Colors.grey.shade100,
body: Obx(() {
......@@ -51,7 +59,12 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
}
final product = _viewModel.product.value;
if (product == null) {
return const Center(child: EmptyWidget());
return Stack(
children: [
const Center(child: EmptyWidget()),
SafeArea(child: Padding(padding: const EdgeInsets.all(8), child: CustomBackButton())),
],
);
}
return Stack(
children: [
......@@ -76,10 +89,7 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CustomBackButton(),
_buildFavoriteButton(),
],
children: [CustomBackButton(), _buildFavoriteButton()],
),
),
),
......@@ -145,9 +155,9 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
product.content?.name ?? '',
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)
product.content?.name ?? '',
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
_buildExpireAndStock(product),
......@@ -171,8 +181,13 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
),
const SizedBox(width: 8),
Expanded(child: Text(product.brand?.name ?? '', style: const TextStyle(fontSize: 14))),
PriceTagWidget(point: product.amountToBePaid ?? 0),
// PriceTagWidget(point: product.amountToBePaid ?? 0),
CustomPointText(
point: product.amountToBePaid ?? 0,
type: product.price?.method,
),
],
),
],
),
......@@ -189,7 +204,8 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
if (hasExpire)
Text('Hạn dùng: ', style: const TextStyle(color: Colors.grey, fontWeight: FontWeight.bold, fontSize: 12)),
if (hasExpire)
Text(product.expire, style: const TextStyle(color: Colors.red, fontWeight: FontWeight.bold, fontSize: 12)),
Text(product.expired ? "Hết hạn" : product.expire,
style: const TextStyle(color: BaseColor.primary500, fontWeight: FontWeight.bold, fontSize: 12)),
if (isOutOfStock)
Container(
margin: const EdgeInsets.only(left: 8),
......@@ -269,7 +285,8 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
Icons.language,
brand.website ?? '',
onTap: () {
final url = brand.website!.startsWith('http') ? brand.website! : 'https://${brand.website}';
final website = brand.website?.trim() ?? "";
final url = website.startsWith('http') ? website : 'https://${brand.website}';
_launchUri(Uri(scheme: url));
},
),
......@@ -295,7 +312,7 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
children: [
Icon(icon, size: 18, color: Colors.black54),
const SizedBox(width: 8),
Expanded(child: Text(value, style: const TextStyle(fontSize: 13, color: Colors.black54))),
Expanded(child: Text(value, style: const TextStyle(fontSize: 13, color: Colors.black))),
const Icon(Icons.chevron_right, color: Colors.black54),
],
),
......@@ -304,11 +321,16 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
}
Widget _buildBottomAction(ProductModel product) {
// if (!(product.isMyProduct
// ? product.customerInfoModel?.status == MyProductStatusType.waiting
// : (product.inStock == true && !(product.expired == true)))) {
// return const SizedBox.shrink();
// }
final bool isOutOfStock = !(product.inStock ?? true);
if (isOutOfStock) {
return const SizedBox.shrink();
}
if (product.isMyProduct && product.customerInfoModel?.status != MyProductStatusType.waiting) {
return const SizedBox.shrink();
}
if (product.expired) {
return const SizedBox.shrink();
}
if (product.isMyProduct) {
return _buildUseButton();
} else if (product.price?.method == CashType.point) {
......@@ -342,8 +364,10 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
backgroundColor: Colors.green,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: const Text('Sử Dụng',
style: TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.bold)),
child: const Text(
'Sử Dụng',
style: TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.bold),
),
),
),
);
......@@ -394,10 +418,7 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
),
),
const SizedBox(width: 12),
Obx(() => Text(
'${_quantity.value}',
style: const TextStyle(fontSize: 16),
)),
Obx(() => Text('${_quantity.value}', style: const TextStyle(fontSize: 16))),
const SizedBox(width: 12),
Container(
decoration: BoxDecoration(
......@@ -453,15 +474,8 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.6),
shape: BoxShape.circle,
),
child: Icon(
Icons.favorite,
color: isFavorite ? BaseColor.primary600 : Colors.white,
size: 24,
),
decoration: BoxDecoration(color: Colors.grey.withOpacity(0.6), shape: BoxShape.circle),
child: Icon(Icons.favorite, color: isFavorite ? BaseColor.primary500 : Colors.white, size: 24),
),
),
),
......
......@@ -7,10 +7,11 @@ import '../models/product_store_model.dart';
class VoucherDetailViewModel extends RestfulApiViewModel {
final int productId;
VoucherDetailViewModel({required this.productId});
var stores = RxList<ProductStoreModel>();
var stores = RxList<ProductStoreModel>();
var product = Rxn<ProductModel>();
var isLoading = false.obs;
var liked = false.obs;
void Function(String message)? onShowAlertError;
@override
void onInit() {
......@@ -20,23 +21,22 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
}
Future<void> toggleFavorite() async {
// if (liked.value) {
// liked.value = false;
// } else {
// liked.value = true;
// }
final value = product.value;
if (value == null) return;
if (value!.liked == true) {
await client.unlikeProduct(value?.likeId ?? 0);
value?.likeId = 0;
Future.microtask(() => liked.value = false);
} else {
final response = await client.likeProduct(productId);
value?.likeId = response.data?.id;
Future.microtask(() => liked.value = (response.data?.id ?? 0) != 0);
try {
if (value!.liked == true) {
await client.unlikeProduct(value?.likeId ?? 0);
value?.likeId = 0;
liked.value = false;
} else {
final response = await client.likeProduct(productId);
value?.likeId = response.data?.id;
liked.value = (response.data?.id ?? 0) != 0;
}
} catch (error) {
onShowAlertError?.call("Error toggling favorite: $error");
print("Error toggling favorite: $error");
}
// product.refresh();
}
Future<void> _getProductDetail() async {
......@@ -47,6 +47,7 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
product.value = response.data;
liked.value = product.value?.liked == true;
} catch (error) {
onShowAlertError?.call("Error fetching product detail: $error");
print("Error fetching product detail: $error");
} finally {
isLoading.value = false;
......@@ -58,8 +59,8 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
final response = await client.getProductStores(productId);
stores.value = response.data ?? [];
} catch (error) {
print("Error fetching product detail: $error");
} finally {
}
onShowAlertError?.call("Error product stores: $error");
print("Error product stores: $error");
} finally {}
}
}
......@@ -18,7 +18,7 @@ part 'product_model.g.dart';
class ProductModel {
final int? id;
@JsonKey(name: 'like_id')
late final int? likeId;
int? likeId;
@JsonKey(name: 'quantity_available')
final int? quantityAvailable;
final ProductContentModel? content;
......@@ -60,6 +60,13 @@ class ProductModel {
return ex.toDate()?.toFormattedString() ?? "";
}
bool get expired {
if (customerInfoModel?.status == MyProductStatusType.expired) return true;
final expireDate = expire.toDateFormat('dd/MM/yyyy');
if (expireDate == null) return false;
return expireDate!.isBefore(DateTime.now());
}
int? get amountToBePaid {
if (previewFlashSale?.isFlashSalePrice == true) {
return previewFlashSale?.price;
......@@ -79,13 +86,6 @@ class ProductModel {
return (likeId ?? 0) != 0;
}
bool get expired {
if (customerInfoModel != null) {
return customerInfoModel?.status == MyProductStatusType.expired;
}
return (quantityAvailable ?? 1) != 0;
}
ProductMediaItem? get banner {
if (media == null) return null;
return media!.firstWhere((item) => item.type == MediaType.banner16_9, orElse: () => media!.first);
......
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