Commit 8d264762 authored by DatHV's avatar DatHV
Browse files

update voucher detail

parent 560b49dd
...@@ -29,4 +29,6 @@ class APIPaths { ...@@ -29,4 +29,6 @@ class APIPaths {
static const String getSearchProducts = "/product/api/v2.0/products/search"; static const String getSearchProducts = "/product/api/v2.0/products/search";
static const String getProductDetail = "/product/api/v2.0/products/%@"; static const String getProductDetail = "/product/api/v2.0/products/%@";
static const String getProductStores = "/product/api/v2.0/product/stores"; 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/%@";
} }
\ No newline at end of file
...@@ -7,7 +7,7 @@ import '../configs/constants.dart'; ...@@ -7,7 +7,7 @@ import '../configs/constants.dart';
import 'model_maker.dart'; import 'model_maker.dart';
enum Method { enum Method {
GET, POST, PUT GET, POST, PUT, DELETE
} }
class RestfulAPIClient { class RestfulAPIClient {
......
...@@ -20,6 +20,7 @@ import '../screen/otp/model/otp_verify_response_model.dart'; ...@@ -20,6 +20,7 @@ import '../screen/otp/model/otp_verify_response_model.dart';
import '../screen/pageDetail/model/campaign_detail_model.dart'; import '../screen/pageDetail/model/campaign_detail_model.dart';
import '../screen/pageDetail/model/detail_page_rule_type.dart'; import '../screen/pageDetail/model/detail_page_rule_type.dart';
import '../screen/splash/splash_screen_viewmodel.dart'; import '../screen/splash/splash_screen_viewmodel.dart';
import '../screen/voucher/models/like_product_reponse_model.dart';
import '../screen/voucher/models/product_store_model.dart'; import '../screen/voucher/models/product_store_model.dart';
import '../screen/voucher/models/search_product_response_model.dart'; import '../screen/voucher/models/search_product_response_model.dart';
import 'model_maker.dart'; import 'model_maker.dart';
...@@ -296,4 +297,21 @@ extension RestfullAPIClientAllApi on RestfulAPIClient { ...@@ -296,4 +297,21 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
return list.map((e) => ProductStoreModel.fromJson(e)).toList(); return list.map((e) => ProductStoreModel.fromJson(e)).toList();
}); });
} }
Future<BaseResponseModel<LikeProductReponseModel>> likeProduct(int id) async {
final body = {"product_id": id};
return requestNormal(APIPaths.productCustomerLikes, Method.POST, body, (data) {
return LikeProductReponseModel.fromJson(data as Json);
});
}
Future<BaseResponseModel<EmptyCodable>> unlikeProduct(int id) async {
final path = APIPaths.productCustomerUnlikes.replaceAll("%@", id.toString());
return requestNormal(
path,
Method.DELETE,
{},
(data) => EmptyCodable.fromJson(data as Json),
);
}
} }
\ No newline at end of file
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/shared/direction_google_map.dart';
import '../models/product_store_model.dart'; import '../models/product_store_model.dart';
class StoreListSection extends StatelessWidget { class StoreListSection extends StatelessWidget {
final List<ProductStoreModel> stores; final List<ProductStoreModel> stores;
final String? brandLogo;
const StoreListSection({Key? key, required this.stores}) : super(key: key); const StoreListSection({Key? key, required this.stores, this.brandLogo}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
...@@ -16,49 +18,48 @@ class StoreListSection extends StatelessWidget { ...@@ -16,49 +18,48 @@ class StoreListSection extends StatelessWidget {
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( decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)),
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( const Text('Địa điểm áp dụng:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
'Địa điểm áp dụng:', const SizedBox(height: 10),
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), // ...stores.map((store) => _buildStoreItem(store)).toList(),
), ...stores.map((store) => InkWell(
const SizedBox(height: 12), onTap: () {
...stores.map((store) => _buildStoreItem(store)).toList(), _onTapStore(store);
},
child: _buildStoreItem(store),
)),
], ],
), ),
); );
} }
_onTapStore(ProductStoreModel store) {
showGoogleMap(lat: store.latitude, lng: store.longitude);
}
Widget _buildStoreItem(ProductStoreModel store) { Widget _buildStoreItem(ProductStoreModel store) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.only(bottom: 12),
child: Row( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Logo Brand Row(
children: [
ClipOval( ClipOval(
child: Image.network( child: Image.network(
"", brandLogo ?? "",
width: 20, width: 20,
height: 20, height: 20,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (_, __, ___) => errorBuilder: (_, __, ___) => Image.asset('assets/images/ic_logo.png', width: 20, height: 20),
Image.asset('assets/images/sample.png', width: 20, height: 20),
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Text(store.name ?? '', style: const TextStyle(fontWeight: FontWeight.w600)),
child: Column( ],
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
store.name ?? '',
style: const TextStyle(fontWeight: FontWeight.w600),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Row( Row(
...@@ -78,9 +79,6 @@ class StoreListSection extends StatelessWidget { ...@@ -78,9 +79,6 @@ class StoreListSection extends StatelessWidget {
), ),
], ],
), ),
),
],
),
); );
} }
} }
...@@ -2,6 +2,7 @@ import 'dart:math'; ...@@ -2,6 +2,7 @@ import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.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:mypoint_flutter_app/screen/voucher/detail/store_list_section.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../resouce/base_color.dart'; import '../../../resouce/base_color.dart';
import '../../../widgets/back_button.dart'; import '../../../widgets/back_button.dart';
import '../../../widgets/custom_empty_widget.dart'; import '../../../widgets/custom_empty_widget.dart';
...@@ -9,6 +10,8 @@ import '../../../widgets/custom_price_tag.dart'; ...@@ -9,6 +10,8 @@ import '../../../widgets/custom_price_tag.dart';
import '../../../widgets/dashed_line.dart'; import '../../../widgets/dashed_line.dart';
import '../../../widgets/image_loader.dart'; import '../../../widgets/image_loader.dart';
import '../../../widgets/measure_size.dart'; import '../../../widgets/measure_size.dart';
import '../models/cash_type.dart';
import '../models/my_product_status_type.dart';
import '../models/product_model.dart'; import '../models/product_model.dart';
import 'voucher_detail_viewmodel.dart'; import 'voucher_detail_viewmodel.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
...@@ -23,8 +26,8 @@ class VoucherDetailScreen extends StatefulWidget { ...@@ -23,8 +26,8 @@ class VoucherDetailScreen extends StatefulWidget {
class _VoucherDetailScreenState extends State<VoucherDetailScreen> { class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
late final int productId; late final int productId;
late final VoucherDetailViewModel _viewModel; late final VoucherDetailViewModel _viewModel;
final GlobalKey _infoKey = GlobalKey();
double _infoHeight = 0; double _infoHeight = 0;
final _quantity = 1.obs;
@override @override
void initState() { void initState() {
...@@ -41,6 +44,7 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> { ...@@ -41,6 +44,7 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Colors.grey.shade100,
body: Obx(() { body: Obx(() {
if (_viewModel.isLoading.value) { if (_viewModel.isLoading.value) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
...@@ -51,26 +55,45 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> { ...@@ -51,26 +55,45 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
} }
return Stack( return Stack(
children: [ children: [
Container( SingleChildScrollView(
color: Colors.grey.shade100,
child: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildHeaderWithInfo(product), _buildHeaderWithInfo(product),
SizedBox(height: max(_infoHeight - 36, 0)), SizedBox(height: max(_infoHeight - 36, 0)),
_buildDetailBlock(product), _buildTextBlock("Chi tiết ưu đãi:", product.content?.detail),
_buildConditionBlock(product), _buildTextBlock("Điều kiện áp dụng:", product.content?.termAndCondition),
SizedBox(height: 8), SizedBox(height: 8),
StoreListSection(stores: _viewModel.stores), StoreListSection(stores: _viewModel.stores, brandLogo: product.brand?.logo ?? ""),
_buildSupportBlock(product),
Container(color: Colors.grey.shade100, child: SizedBox(height: 64)),
],
),
),
SafeArea(
child: Padding(
padding: const EdgeInsets.all(8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CustomBackButton(),
_buildFavoriteButton(),
], ],
), ),
), ),
), ),
SafeArea(child: Padding(padding: const EdgeInsets.all(8), child: CustomBackButton())), // SafeArea(child: Padding(padding: const EdgeInsets.all(8), child: CustomBackButton())),
], ],
); );
}), }),
bottomNavigationBar: Obx(() {
final product = _viewModel.product.value;
if (product == null) {
return const SizedBox.shrink();
}
return _buildBottomAction(product);
}),
); );
} }
...@@ -116,12 +139,16 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> { ...@@ -116,12 +139,16 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 8, offset: const Offset(0, 4))], boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 4))],
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text(product.content?.name ?? '', style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)), Text(
product.content?.name ?? '',
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)
),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildExpireAndStock(product), _buildExpireAndStock(product),
const SizedBox(height: 16), const SizedBox(height: 16),
...@@ -155,37 +182,19 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> { ...@@ -155,37 +182,19 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
Widget _buildExpireAndStock(ProductModel product) { Widget _buildExpireAndStock(ProductModel product) {
final bool isOutOfStock = !(product.inStock ?? true); final bool isOutOfStock = !(product.inStock ?? true);
final bool hasExpire = product.expire.isNotEmpty; final bool hasExpire = product.expire.isNotEmpty;
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
if (hasExpire) if (hasExpire)
Text( Text('Hạn dùng: ', style: const TextStyle(color: Colors.grey, fontWeight: FontWeight.bold, fontSize: 12)),
'Hạn dùng: ',
style: const TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
if (hasExpire) if (hasExpire)
Text( Text(product.expire, style: const TextStyle(color: Colors.red, fontWeight: FontWeight.bold, fontSize: 12)),
product.expire,
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
if (isOutOfStock) if (isOutOfStock)
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),
decoration: BoxDecoration( decoration: BoxDecoration(color: Colors.grey, borderRadius: BorderRadius.circular(4)),
color: Colors.grey,
borderRadius: BorderRadius.circular(4),
),
child: const Text( child: const Text(
'Tạm hết', 'Tạm hết',
style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold), style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
...@@ -196,11 +205,11 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> { ...@@ -196,11 +205,11 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
} }
Widget _buildDetailBlock(ProductModel product) { Widget _buildDetailBlock(ProductModel product) {
return _buildTextBlock("Chi tiết ưu đãi", product.content?.detail); return _buildTextBlock("Chi tiết ưu đãi:", product.content?.detail);
} }
Widget _buildConditionBlock(ProductModel product) { Widget _buildConditionBlock(ProductModel product) {
return _buildTextBlock("Điều kiện áp dụng", product.content?.termAndCondition); return _buildTextBlock("Điều kiện áp dụng:", product.content?.termAndCondition);
} }
Widget _buildTextBlock(String title, String? content) { Widget _buildTextBlock(String title, String? content) {
...@@ -214,9 +223,249 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> { ...@@ -214,9 +223,249 @@ class _VoucherDetailScreenState extends State<VoucherDetailScreen> {
children: [ children: [
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 8), const SizedBox(height: 8),
HtmlWidget(content), HtmlWidget(content, textStyle: const TextStyle(fontSize: 13, color: Colors.black54)),
],
),
);
}
Widget _buildSupportBlock(ProductModel product) {
final brand = product.brand;
if (brand == null) return const SizedBox.shrink();
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Hỗ trợ:', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(
'Nếu bạn gặp bất kỳ vấn đề gì với voucher này, xin vui lòng liên hệ ${brand.name ?? ''}',
style: const TextStyle(fontSize: 13, color: Colors.black54),
),
const SizedBox(height: 12),
if ((brand.phone ?? '').isNotEmpty)
_buildContactRow(
Icons.phone,
brand.phone ?? '',
onTap: () async {
final Uri phoneUri = Uri.parse('tel:${brand.phone}');
_launchUri(phoneUri);
},
),
if ((brand.email ?? '').isNotEmpty)
_buildContactRow(
Icons.email_outlined,
brand.email ?? '',
onTap: () async {
final Uri emailUri = Uri.parse('mailto:${brand.email}');
_launchUri(emailUri);
},
),
if ((brand.website ?? '').isNotEmpty)
_buildContactRow(
Icons.language,
brand.website ?? '',
onTap: () {
final url = brand.website!.startsWith('http') ? brand.website! : 'https://${brand.website}';
_launchUri(Uri(scheme: url));
},
),
],
),
);
}
_launchUri(Uri uri) async {
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
} else {
throw 'Could not launch $uri';
}
}
Widget _buildContactRow(IconData icon, String value, {VoidCallback? onTap}) {
return InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
Icon(icon, size: 18, color: Colors.black54),
const SizedBox(width: 8),
Expanded(child: Text(value, style: const TextStyle(fontSize: 13, color: Colors.black54))),
const Icon(Icons.chevron_right, color: Colors.black54),
],
),
),
);
}
Widget _buildBottomAction(ProductModel product) {
// if (!(product.isMyProduct
// ? product.customerInfoModel?.status == MyProductStatusType.waiting
// : (product.inStock == true && !(product.expired == true)))) {
// return const SizedBox.shrink();
// }
if (product.isMyProduct) {
return _buildUseButton();
} else if (product.price?.method == CashType.point) {
return _buildExchangeButton();
} else {
return _buildBuyButtonWithCounter();
}
}
Widget _buildBottomActionContainer({required Widget child}) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
decoration: const BoxDecoration(
color: Colors.white,
boxShadow: [BoxShadow(color: Colors.black54, blurRadius: 8, offset: Offset(0, 4))],
),
child: SafeArea(top: false, child: child),
);
}
Widget _buildUseButton() {
return _buildBottomActionContainer(
child: SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
onPressed: () {
// TODO: Handle sử dụng voucher
},
style: ElevatedButton.styleFrom(
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)),
),
),
);
}
Widget _buildExchangeButton() {
return _buildBottomActionContainer(
child: SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
onPressed: () {
// TODO: Handle đổi ưu đãi
},
style: ElevatedButton.styleFrom(
backgroundColor: BaseColor.primary500,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: const Text(
'Đổi Ưu Đãi',
style: TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.bold),
),
),
),
);
}
Widget _buildBuyButtonWithCounter() {
return _buildBottomActionContainer(
child: Row(
children: [
Row(
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(50),
),
child: IconButton(
icon: const Icon(Icons.remove, color: Colors.black),
onPressed: () {
if (_quantity.value > 1) {
_quantity.value--;
// TODO: update state
}
},
),
),
const SizedBox(width: 12),
Obx(() => Text(
'${_quantity.value}',
style: const TextStyle(fontSize: 16),
)),
const SizedBox(width: 12),
Container(
decoration: BoxDecoration(
color: BaseColor.primary500,
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(50),
),
child: IconButton(
icon: const Icon(Icons.add, color: Colors.white),
onPressed: () {
_quantity.value++;
// TODO: update state
},
),
),
],
),
const SizedBox(width: 36),
Expanded(
child: SizedBox(
height: 48,
child: ElevatedButton(
onPressed: () {
// TODO: Handle mua ngay
},
style: ElevatedButton.styleFrom(
backgroundColor: BaseColor.primary500,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: const Text(
'Mua ngay',
style: TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.bold),
),
),
),
),
], ],
), ),
); );
} }
Widget _buildFavoriteButton() {
return Obx(() {
final isFavorite = _viewModel.liked.value;
return Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.only(top: 8, right: 8),
child: GestureDetector(
onTap: () {
_viewModel.toggleFavorite();
},
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,
),
),
),
),
);
});
}
} }
...@@ -10,6 +10,7 @@ class VoucherDetailViewModel extends RestfulApiViewModel { ...@@ -10,6 +10,7 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
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;
@override @override
void onInit() { void onInit() {
...@@ -18,12 +19,33 @@ class VoucherDetailViewModel extends RestfulApiViewModel { ...@@ -18,12 +19,33 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
_getProductStores(); _getProductStores();
} }
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);
}
// product.refresh();
}
Future<void> _getProductDetail() async { Future<void> _getProductDetail() async {
if (isLoading.value) return; if (isLoading.value) return;
try { try {
isLoading.value = true; isLoading.value = true;
final response = await client.getProduct(productId); final response = await client.getProduct(productId);
product.value = response.data; product.value = response.data;
liked.value = product.value?.liked == true;
} catch (error) { } catch (error) {
print("Error fetching product detail: $error"); print("Error fetching product detail: $error");
} finally { } finally {
......
import 'package:json_annotation/json_annotation.dart';
part 'like_product_reponse_model.g.dart';
@JsonSerializable()
class LikeProductReponseModel {
@JsonKey(name: 'like_id')
final int? id;
LikeProductReponseModel({
this.id,
});
factory LikeProductReponseModel.fromJson(Map<String, dynamic> json) => _$LikeProductReponseModelFromJson(json);
Map<String, dynamic> toJson() => _$LikeProductReponseModelToJson(this);
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'like_product_reponse_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
LikeProductReponseModel _$LikeProductReponseModelFromJson(
Map<String, dynamic> json,
) => LikeProductReponseModel(id: (json['like_id'] as num?)?.toInt());
Map<String, dynamic> _$LikeProductReponseModelToJson(
LikeProductReponseModel instance,
) => <String, dynamic>{'like_id': instance.id};
...@@ -17,6 +17,8 @@ part 'product_model.g.dart'; ...@@ -17,6 +17,8 @@ part 'product_model.g.dart';
@JsonSerializable() @JsonSerializable()
class ProductModel { class ProductModel {
final int? id; final int? id;
@JsonKey(name: 'like_id')
late final int? likeId;
@JsonKey(name: 'quantity_available') @JsonKey(name: 'quantity_available')
final int? quantityAvailable; final int? quantityAvailable;
final ProductContentModel? content; final ProductContentModel? content;
...@@ -36,6 +38,7 @@ class ProductModel { ...@@ -36,6 +38,7 @@ class ProductModel {
ProductModel({ ProductModel({
this.id, this.id,
this.likeId,
this.quantityAvailable, this.quantityAvailable,
this.content, this.content,
this.price, this.price,
...@@ -72,6 +75,10 @@ class ProductModel { ...@@ -72,6 +75,10 @@ class ProductModel {
return (quantityAvailable ?? 1) != 0; return (quantityAvailable ?? 1) != 0;
} }
bool get liked {
return (likeId ?? 0) != 0;
}
bool get expired { bool get expired {
if (customerInfoModel != null) { if (customerInfoModel != null) {
return customerInfoModel?.status == MyProductStatusType.expired; return customerInfoModel?.status == MyProductStatusType.expired;
...@@ -85,5 +92,6 @@ class ProductModel { ...@@ -85,5 +92,6 @@ class ProductModel {
} }
factory ProductModel.fromJson(Map<String, dynamic> json) => _$ProductModelFromJson(json); factory ProductModel.fromJson(Map<String, dynamic> json) => _$ProductModelFromJson(json);
Map<String, dynamic> toJson() => _$ProductModelToJson(this); Map<String, dynamic> toJson() => _$ProductModelToJson(this);
} }
\ No newline at end of file
...@@ -8,6 +8,7 @@ part of 'product_model.dart'; ...@@ -8,6 +8,7 @@ part of 'product_model.dart';
ProductModel _$ProductModelFromJson(Map<String, dynamic> json) => ProductModel( ProductModel _$ProductModelFromJson(Map<String, dynamic> json) => ProductModel(
id: (json['id'] as num?)?.toInt(), id: (json['id'] as num?)?.toInt(),
likeId: (json['like_id'] as num?)?.toInt(),
quantityAvailable: (json['quantity_available'] as num?)?.toInt(), quantityAvailable: (json['quantity_available'] as num?)?.toInt(),
content: content:
json['content'] == null json['content'] == null
...@@ -57,6 +58,7 @@ ProductModel _$ProductModelFromJson(Map<String, dynamic> json) => ProductModel( ...@@ -57,6 +58,7 @@ ProductModel _$ProductModelFromJson(Map<String, dynamic> json) => ProductModel(
Map<String, dynamic> _$ProductModelToJson(ProductModel instance) => Map<String, dynamic> _$ProductModelToJson(ProductModel instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'like_id': instance.likeId,
'quantity_available': instance.quantityAvailable, 'quantity_available': instance.quantityAvailable,
'content': instance.content, 'content': instance.content,
'price': instance.price, 'price': instance.price,
......
import 'package:url_launcher/url_launcher.dart';
Future<void> showGoogleMap({required double? lat, required double? lng}) async {
if (lat == null || lng == null) return;
final googleMapsSchemeUrl = Uri.parse('comgooglemaps://');
final googleMapsAppUrl = Uri.parse('comgooglemaps-x-callback://?saddr=&daddr=$lat,$lng&directionsmode=driving');
final googleMapsWebUrl = Uri.parse('https://www.google.com/maps/dir/?saddr=&daddr=$lat,$lng&directionsmode=driving');
// Kiểm tra xem device có cài app Google Maps không
if (await canLaunchUrl(googleMapsSchemeUrl)) {
// Có app Google Maps -> mở app
if (await canLaunchUrl(googleMapsAppUrl)) {
await launchUrl(googleMapsAppUrl);
}
} else {
// Không có app -> mở trên trình duyệt
if (await canLaunchUrl(googleMapsWebUrl)) {
await launchUrl(googleMapsWebUrl, mode: LaunchMode.externalApplication);
}
}
}
\ No newline at end of file
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