Commit fda33894 authored by DatHV's avatar DatHV
Browse files

cap nhat giao dien

parent 75178f29
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import '../../base/restful_api_viewmodel.dart';
import 'models/pipi_detail_model.dart';
class PipiDetailViewModel extends RestfulApiViewModel {
var items = RxList<PipiSupportItemModel>();
PipiDetailViewModel();
@override
void onInit() {
super.onInit();
fetchPipiDetails();
}
Future<void> fetchPipiDetails() async {
try {
final response = await client.getPipiDetail();
items.value = response.data?.items ?? [];
} catch (error) {
print("Error fetching Pipi details: $error");
}
}
}
\ No newline at end of file
......@@ -4,6 +4,7 @@ import 'package:mypoint_flutter_app/configs/constants.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import 'package:mypoint_flutter_app/screen/otp/forgot_pass_otp_repository.dart';
import 'package:mypoint_flutter_app/screen/otp/otp_screen.dart';
import 'package:mypoint_flutter_app/shared/router_gage.dart';
import '../../base/base_response_model.dart';
import '../../base/restful_api_viewmodel.dart';
import '../../model/auth/login_token_response_model.dart';
......@@ -71,7 +72,7 @@ class LoginViewModel extends RestfulApiViewModel {
final userProfile = value.data;
if (value.isSuccess && userProfile != null) {
await DataPreference.instance.saveUserProfile(userProfile);
Get.to(MainTabScreen());
Get.toNamed(mainScreen);
} else {
DataPreference.instance.clearLoginToken();
final mgs = value.errorMessage ?? Constants.commonError;
......
import 'package:json_annotation/json_annotation.dart';
part 'news_item_model.g.dart';
@JsonSerializable()
class NewsItemModel {
final String? thumbnail;
@JsonKey(name: 'page_id')
final String? pageId;
@JsonKey(name: 'sub_title')
final String? subTitle;
final String? title;
@JsonKey(name: 'publish_at_date')
final String? publishAtDate;
final String? chapeau;
NewsItemModel({
this.thumbnail,
this.pageId,
this.subTitle,
this.title,
this.publishAtDate,
this.chapeau,
});
factory NewsItemModel.fromJson(Map<String, dynamic> json) =>
_$NewsItemModelFromJson(json);
Map<String, dynamic> toJson() => _$NewsItemModelToJson(this);
}
\ No newline at end of file
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'news_item_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
NewsItemModel _$NewsItemModelFromJson(Map<String, dynamic> json) =>
NewsItemModel(
thumbnail: json['thumbnail'] as String?,
pageId: json['page_id'] as String?,
subTitle: json['sub_title'] as String?,
title: json['title'] as String?,
publishAtDate: json['publish_at_date'] as String?,
chapeau: json['chapeau'] as String?,
);
Map<String, dynamic> _$NewsItemModelToJson(NewsItemModel instance) =>
<String, dynamic>{
'thumbnail': instance.thumbnail,
'page_id': instance.pageId,
'sub_title': instance.subTitle,
'title': instance.title,
'publish_at_date': instance.publishAtDate,
'chapeau': instance.chapeau,
};
import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart';
import '../faqs/faqs_model.dart';
class PageItemWidget extends StatelessWidget {
final PageItemModel item;
const PageItemWidget({super.key, required this.item});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 16),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 3, offset: const Offset(2, 1))],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Nội dung văn bản
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.title ?? '', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14)),
const SizedBox(height: 4),
Text(item.chapeau ?? '', style: const TextStyle(fontSize: 13, color: Colors.black87)),
],
),
),
const SizedBox(width: 12),
// Ảnh thumbnail
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: loadNetworkImage(
url: item.thumbnail ?? '',
width: 64,
height: 64,
fit: BoxFit.cover,
placeholderAsset: "assets/images/bg_default_11.png",
),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../shared/router_gage.dart';
import '../../../widgets/custom_empty_widget.dart';
import '../../../widgets/custom_navigation_bar.dart';
import 'news_item_widget.dart';
import 'news_list_viewmodel.dart';
class NewsListScreen extends StatefulWidget {
const NewsListScreen({super.key});
@override
_NewsListScreenState createState() => _NewsListScreenState();
}
class _NewsListScreenState extends State<NewsListScreen> {
late final NewsListViewModel _viewModel = Get.put(NewsListViewModel());
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
appBar: CustomNavigationBar(title: "MyPoint có gì hot?"),
body: Column(
children: [
Expanded(
child: Obx(() {
if (_viewModel.newsList.value.isEmpty) {
return const Center(child: EmptyWidget());
}
return RefreshIndicator(
onRefresh: () => _viewModel.getNewsList(isRefresh: true),
child: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemCount: _viewModel.newsList.length,
itemBuilder: (context, index) {
if (index >= _viewModel.newsList.length) {
_viewModel.getNewsList(isRefresh: false);
return const Center(
child: Padding(padding: EdgeInsets.all(16), child: CircularProgressIndicator()),
);
}
final news = _viewModel.newsList.value[index];
return GestureDetector(
onTap: () {
Get.toNamed(campaignDetailScreen, arguments: {"id": news.pageId ?? ""});
},
child: PageItemWidget(item: news),
);
},
),
);
}),
),
],
),
);
}
}
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import '../../base/restful_api_viewmodel.dart';
import '../../preference/data_preference.dart';
import '../faqs/faqs_model.dart';
class NewsListViewModel extends RestfulApiViewModel {
String folderUri;
var newsList = <PageItemModel>[].obs;
var isLoading = false.obs;
var _canLoadMore = true;
int start = 0;
int limit = 20;
NewsListViewModel({this.folderUri = "TIN-TUC"});
@override
onInit() {
super.onInit();
getNewsList();
}
Future<void> getNewsList({bool isRefresh = false}) async {
if (isLoading.value) return;
if (!isRefresh && !_canLoadMore) return;
showLoading();
isLoading(true);
final body = {
"folder_uri": folderUri,
"start": isRefresh ? 0 : newsList.length,
"limit": limit,
};
client.websiteFolderGetPageList(body).then((value) {
hideLoading();
isLoading(false);
_canLoadMore = value.data?.items?.length == limit;
if (isRefresh) {
newsList.value.clear();
}
newsList.addAll(value.data?.items ?? []);
});
}
}
\ No newline at end of file
......@@ -189,7 +189,7 @@ class _OnboardingScreenState extends BaseState<OnboardingScreen> with BasicState
const TextSpan(text: "Bằng việc tiếp tục, bạn đã đọc và đồng ý với "),
WidgetSpan(
child: GestureDetector(
onTap: () => Get.to(CampaignDetailScreen(type: DetailPageRuleType.termsOfUse)),
onTap: () => Get.toNamed(campaignDetailScreen, arguments: {"type": DetailPageRuleType.termsOfUse}),
child: const Text(
"Điều khoản sử dụng",
style: TextStyle(
......
......@@ -3,23 +3,20 @@ import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:get/get.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../directional/directional_action_type.dart';
import '../../directional/directional_screen.dart';
import '../../extensions/string_extension.dart'; // tuỳ dự án
import '../../extensions/string_extension.dart';
import '../../resouce/base_color.dart';
import '../../shared/router_gage.dart';
import '../../widgets/back_button.dart';
import '../../widgets/network_image_with_aspect_ratio.dart'; // widget custom
import '../../widgets/network_image_with_aspect_ratio.dart';
import 'campaign_detail_viewmodel.dart';
import 'campaign_item_page_widget.dart';
import 'model/campaign_detail_item_model.dart';
import 'model/campaign_detail_model.dart';
import 'model/detail_page_rule_type.dart';
import 'model/media_type_item_campaign.dart';
class CampaignDetailScreen extends BaseScreen {
final DetailPageRuleType? type;
final String? pageId;
const CampaignDetailScreen({super.key, this.type, this.pageId});
const CampaignDetailScreen({super.key});
@override
State<CampaignDetailScreen> createState() => _CampaignDetailScreenState();
......@@ -38,7 +35,14 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
});
}
});
_viewModel.fetchData(widget.type, widget.pageId);
DetailPageRuleType? type;
String? pageId;
final args = Get.arguments;
if (args is Map) {
type = args['type'];
pageId = args['id'];
}
_viewModel.fetchData(type, pageId);
}
@override
......@@ -70,10 +74,10 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
color: Colors.grey.shade200,
child: const Center(child: CircularProgressIndicator()),
),
errorWidget: Image.asset("assets/images/bg_header_campaign_default.png", fit: BoxFit.cover),
errorWidget: Image.asset("assets/images/bg_default_169.png", fit: BoxFit.cover),
)
else
Image.asset("assets/images/bg_header_campaign_default.png", fit: BoxFit.cover),
Image.asset("assets/images/bg_default_169.png", fit: BoxFit.cover),
Transform.translate(
offset: const Offset(0, -32),
child: Container(
......@@ -90,7 +94,7 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
Text(publishDate, style: const TextStyle(color: Colors.grey, fontSize: 14)),
const SizedBox(height: 8),
Text(title, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
const SizedBox(height: 8),
_buildItems(items),
const SizedBox(height: 8),
],
......@@ -100,11 +104,7 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
],
),
),
Positioned(
top: MediaQuery.of(context).padding.top + 8,
left: 8,
child: CustomBackButton(),
),
Positioned(top: MediaQuery.of(context).padding.top + 8, left: 8, child: CustomBackButton()),
if (buttonOn == "1") _bottomButton(pageDetail),
],
);
......@@ -127,20 +127,25 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
child: Center(
child: Column(
children: [
SizedBox(height: 12,),
SizedBox(height: 12),
ElevatedButton(
onPressed: () {
DirectionalScreen(
clickActionType: pageDetail?.buttonClickActionType,
clickActionParam: pageDetail?.buttonClickActionParam).begin();
print("pageDetail?.directionalScreen");
print(pageDetail?.directionalScreen);
print(pageDetail?.buttonClickActionType);
print(pageDetail?.buttonClickActionParam);
pageDetail?.directionalScreen?.begin();
},
style: ElevatedButton.styleFrom(
backgroundColor: parseHexColor(buttonColor),
minimumSize: const Size.fromHeight(48),
),
child: Text(buttonName, style: TextStyle(color: parseHexColor(buttonTextColor), fontWeight: FontWeight.bold)),
child: Text(
buttonName,
style: TextStyle(color: parseHexColor(buttonTextColor), fontWeight: FontWeight.bold),
),
),
SizedBox(height: MediaQuery.of(context).padding.bottom)
SizedBox(height: MediaQuery.of(context).padding.bottom),
],
),
),
......@@ -151,8 +156,8 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
Widget _buildItems(List<CampaignDetailItemModel> items) {
List<Widget> widgets = [];
for (var item in items) {
final mediaType = item.mediaType?.value ?? ""; //toLowerCase() ?? "";
if (mediaType == MediaTypeItemCampaign.image.name) {
final mediaType = (item.mediaType ?? "").toLowerCase();
if (mediaType == MediaTypeItemCampaign.image.key || mediaType == MediaTypeItemCampaign.imageLink.key) {
if (item.contentText != null && item.contentText!.isNotEmpty) {
widgets.add(
Padding(
......@@ -169,10 +174,27 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
),
);
}
} else if (mediaType == MediaTypeItemCampaign.text.name) {
} else if (mediaType == MediaTypeItemCampaign.text.key) {
if (item.contentText != null && item.contentText!.isNotEmpty) {
widgets.add(Padding(padding: const EdgeInsets.only(bottom: 16), child: HtmlWidget(item.contentText!)));
}
} else if (mediaType == MediaTypeItemCampaign.pageLink.key) {
if (item.pages?.isNotEmpty == true) {
for (var item in item.pages!) {
widgets.add(
CampaignItemPageWidget(
itemPageCampaign: item,
onTap: () async {
Get.offNamed(
campaignDetailScreen,
arguments: {"id": item.pageId},
preventDuplicates: false,
);
},
),
);
}
}
}
}
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: widgets);
......
import 'package:flutter/material.dart';
import 'model/campaign_detail_item_model.dart';
class CampaignItemPageWidget extends StatelessWidget {
final CampaignItemPageModel itemPageCampaign;
final VoidCallback? onTap;
const CampaignItemPageWidget({super.key, required this.itemPageCampaign, this.onTap});
@override
Widget build(BuildContext context) {
final hasImage = itemPageCampaign.thumbnail != null && itemPageCampaign.thumbnail!.isNotEmpty;
return GestureDetector(
onTap: onTap,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 2, vertical: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
blurRadius: 6,
offset: const Offset(2, 2),
color: Colors.black.withOpacity(0.1),
)
],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: _buildTextContent(),
),
if (hasImage) const SizedBox(width: 12),
if (hasImage)
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network(
itemPageCampaign.thumbnail!,
width: 60,
height: 60,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => const Icon(Icons.image_not_supported),
),
),
],
),
),
);
}
Widget _buildTextContent() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (itemPageCampaign.title != null)
Text(
itemPageCampaign.title!,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
const SizedBox(height: 4),
if (itemPageCampaign.subTitle != null && itemPageCampaign.subTitle!.isNotEmpty)
Text(
itemPageCampaign.subTitle!,
style: const TextStyle(fontSize: 13),
),
if (itemPageCampaign.chapeau != null && itemPageCampaign.chapeau!.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
itemPageCampaign.chapeau!,
style: const TextStyle(fontSize: 13, color: Colors.black87),
),
),
],
);
}
}
......@@ -10,21 +10,60 @@ class CampaignDetailItemModel {
@JsonKey(name: "content_text")
String? contentText;
@JsonKey(name: "media_type", fromJson: MediaTypeItemCampaign.fromString, toJson: _mediaTypeToJson)
MediaTypeItemCampaign? mediaType;
@JsonKey(name: "media_type")
String? mediaType;
List<CampaignItemPageModel>? pages;
CampaignDetailItemModel({
this.contentCaption,
this.contentText,
this.mediaType,
this.pages,
});
factory CampaignDetailItemModel.fromJson(Map<String, dynamic> json) =>
_$CampaignDetailItemModelFromJson(json);
Map<String, dynamic> toJson() => _$CampaignDetailItemModelToJson(this);
/// 🎯 Helper để convert Enum sang String khi serialize JSON
static String? _mediaTypeToJson(MediaTypeItemCampaign? type) => type?.toJson();
}
class CampaignItemPageModel {
final String? publishAtDate;
final String? title;
final String? pageId;
final String? chapeau;
final String? subTitle;
final String? thumbnail;
const CampaignItemPageModel({
this.publishAtDate,
this.title,
this.pageId,
this.chapeau,
this.subTitle,
this.thumbnail,
});
factory CampaignItemPageModel.fromJson(Map<String, dynamic> json) {
return CampaignItemPageModel(
publishAtDate: json['publish_at_date'] as String?,
title: json['title'] as String?,
pageId: json['page_id'] as String?,
chapeau: json['chapeau'] as String?,
subTitle: json['sub_title'] as String?,
thumbnail: json['thumbnail'] as String?,
);
}
Map<String, dynamic> toJson() {
return {
'publish_at_date': publishAtDate,
'title': title,
'page_id': pageId,
'chapeau': chapeau,
'sub_title': subTitle,
'thumbnail': thumbnail,
};
}
}
......@@ -11,7 +11,13 @@ CampaignDetailItemModel _$CampaignDetailItemModelFromJson(
) => CampaignDetailItemModel(
contentCaption: json['content_caption'] as String?,
contentText: json['content_text'] as String?,
mediaType: MediaTypeItemCampaign.fromString(json['media_type'] as String?),
mediaType: json['media_type'] as String?,
pages:
(json['pages'] as List<dynamic>?)
?.map(
(e) => CampaignItemPageModel.fromJson(e as Map<String, dynamic>),
)
.toList(),
);
Map<String, dynamic> _$CampaignDetailItemModelToJson(
......@@ -19,5 +25,6 @@ Map<String, dynamic> _$CampaignDetailItemModelToJson(
) => <String, dynamic>{
'content_caption': instance.contentCaption,
'content_text': instance.contentText,
'media_type': CampaignDetailItemModel._mediaTypeToJson(instance.mediaType),
'media_type': instance.mediaType,
'pages': instance.pages,
};
import 'package:json_annotation/json_annotation.dart';
import 'package:mypoint_flutter_app/directional/directional_screen.dart';
import 'campaign_detail_item_model.dart';
part 'campaign_detail_model.g.dart';
......@@ -36,6 +37,13 @@ class CampaignDetailModel {
this.items,
});
DirectionalScreen? get directionalScreen {
return DirectionalScreen.build(
clickActionType: buttonClickActionType,
clickActionParam: buttonClickActionParam,
);
}
factory CampaignDetailModel.fromJson(Map<String, dynamic> json) => _$CampaignDetailModelFromJson(json);
Map<String, dynamic> toJson() => _$CampaignDetailModelToJson(this);
}
......
import 'package:get/get.dart';
import 'package:json_annotation/json_annotation.dart';
/// 🎯 Enum ánh xạ với String
enum MediaTypeItemCampaign {
image("image"),
text("text");
image,
imageLink,
pageLink,
text,
}
final String value;
const MediaTypeItemCampaign(this.value);
/// 🎯 Chuyển từ String sang Enum
static MediaTypeItemCampaign? fromString(String? value) {
return MediaTypeItemCampaign.values.firstWhereOrNull((e) => e.value == value);
extension MediaTypeItemCampaignExtension on MediaTypeItemCampaign {
String get key {
switch (this) {
case MediaTypeItemCampaign.image:
return "image";
case MediaTypeItemCampaign.imageLink:
return "image_link";
case MediaTypeItemCampaign.pageLink:
return "page_link";
case MediaTypeItemCampaign.text:
return "text";
}
}
/// 🎯 Chuyển từ Enum sang String
String toJson() => value;
}
\ No newline at end of file
......@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/screen/support/support_item_model.dart';
import 'package:mypoint_flutter_app/screen/support/support_screen_viewmodel.dart';
import 'package:mypoint_flutter_app/shared/router_gage.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../resouce/base_color.dart';
......@@ -43,9 +44,9 @@ class _SupportScreenState extends State<SupportScreen> {
case SupportItemType.question:
Get.to(FAQScreen());
case SupportItemType.termsOfUse:
Get.to(CampaignDetailScreen(type: DetailPageRuleType.termsOfUse));
Get.toNamed(campaignDetailScreen, arguments: {"type": DetailPageRuleType.termsOfUse});
case SupportItemType.privacyPolicy:
Get.to(CampaignDetailScreen(type: DetailPageRuleType.privacyPolicy));
Get.toNamed(campaignDetailScreen, arguments: {"type": DetailPageRuleType.privacyPolicy});
}
}
......
......@@ -43,7 +43,11 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
int? customerProductId;
final args = Get.arguments;
if (args is Map) {
if (args is int) {
productId = args;
} else if (args is String) {
productId = int.tryParse(args);
} else if (args is Map) {
productId = args['productId'];
customerProductId = args['customerProductId'];
}
......
......@@ -37,6 +37,4 @@ enum MyProductStatusType {
return "voucher.expired_voucher_empty";
}
}
// Nếu bạn cần thêm color hoặc các property khác thì viết tiếp ở đây
}
\ No newline at end of file
import 'package:flutter/material.dart';
class VoucherSectionTitle extends StatelessWidget {
class HeaderSectionTitle extends StatelessWidget {
final String title;
final VoidCallback? onViewAll; // 👈 Optional
const VoucherSectionTitle({
const HeaderSectionTitle({
super.key,
required this.title,
this.onViewAll, // 👈 Nếu null thì không hiển thị button
......
......@@ -50,16 +50,16 @@ class VoucherTabScreen extends StatelessWidget {
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: [
const VoucherSectionTitle(title: 'Ưu đãi từ nhà mạng'),
const HeaderSectionTitle(title: 'Ưu đãi từ nhà mạng'),
const VoucherActionMenu(),
VoucherSectionTitle(
HeaderSectionTitle(
title: 'Săn ưu đãi',
onViewAll: () {
Get.toNamed(vouchersScreen, arguments: {"isHotProduct": true});
},
),
VoucherItemGrid(items: viewModel.hotProducts),
const VoucherSectionTitle(title: 'Tất cả ưu đãi'),
const HeaderSectionTitle(title: 'Tất cả ưu đãi'),
VoucherItemList(items: viewModel.allProducts),
if (viewModel.isLoadMore.value)
const Padding(
......
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart';
import '../../shared/router_gage.dart';
import '../../widgets/custom_navigation_bar.dart';
import '../faqs/faqs_model.dart';
import '../news/news_list_viewmodel.dart';
class VplayGameCenterScreen extends StatelessWidget {
const VplayGameCenterScreen({super.key});
@override
Widget build(BuildContext context) {
final viewModel = Get.put(NewsListViewModel(folderUri: "GAME-VPLAY"));
final width = MediaQuery.of(context).size.width;
final space = 12.0;
final itemWidth = (width - space * 3) / 2;
return Scaffold(
appBar: CustomNavigationBar(title: "Trung tâm trò chơi"),
body: Obx(() {
final items = viewModel.newsList.value;
return Padding(
padding: const EdgeInsets.all(16.0),
child: GridView.builder(
itemCount: items.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: space,
mainAxisSpacing: space,
childAspectRatio: itemWidth/(itemWidth + 60),
),
itemBuilder: (context, index) {
final item = items[index];
return GestureDetector(
onTap: () {
Get.toNamed(campaignDetailScreen, arguments: {"id": item.pageId ?? ""});
},
child: _buildVplayGameItem(item, itemWidth),
);
},
),
);
}),
);
}
Widget _buildVplayGameItem(PageItemModel item, double itemWidth) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: loadNetworkImage(
url: item.thumbnail ?? '',
width: itemWidth,
height: itemWidth,
fit: BoxFit.cover,
placeholderAsset: "assets/images/bg_default_11.png",
),
),
const SizedBox(height: 4),
Text(
item.title ?? '',
textAlign: TextAlign.center,
maxLines: 2,
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
],
);
}
}
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