Commit 6b980613 authored by DatHV's avatar DatHV
Browse files

update project structure

parent bfff9e47
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/num_extension.dart'; import 'package:mypoint_flutter_app/core/utils/extensions/num_extension.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart'; import 'package:mypoint_flutter_app/shared/widgets/image_loader.dart';
import '../../../widgets/custom_empty_widget.dart'; import '../../shared/widgets/custom_empty_widget.dart';
import '../../widgets/custom_navigation_bar.dart'; import '../../shared/widgets/custom_navigation_bar.dart';
import 'history_point_cashback_viewmodel.dart'; import 'history_point_cashback_viewmodel.dart';
import 'models/history_point_cashback_model.dart'; import 'models/history_point_cashback_model.dart';
...@@ -26,7 +26,6 @@ class _HistoryPointCashBackScreenState extends State<HistoryPointCashBackScreen> ...@@ -26,7 +26,6 @@ class _HistoryPointCashBackScreenState extends State<HistoryPointCashBackScreen>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return Scaffold( return Scaffold(
appBar: CustomNavigationBar(title: "Lịch sử hoàn điểm"), appBar: CustomNavigationBar(title: "Lịch sử hoàn điểm"),
body: Column( body: Column(
...@@ -62,12 +61,13 @@ class _HistoryPointCashBackScreenState extends State<HistoryPointCashBackScreen> ...@@ -62,12 +61,13 @@ class _HistoryPointCashBackScreenState extends State<HistoryPointCashBackScreen>
), ),
), ),
), ),
if (_viewModel.orders.isEmpty) Expanded(
Expanded(child: EmptyWidget(size: Size(screenWidth / 2, screenWidth / 2))) child: Obx(
else () {
Obx( if (_viewModel.orders.isEmpty) {
() => Expanded( return EmptyWidget(isLoading: _viewModel.isLoading.value);
child: RefreshIndicator( }
return RefreshIndicator(
onRefresh: () async { onRefresh: () async {
_viewModel.freshData(isRefresh: true); _viewModel.freshData(isRefresh: true);
}, },
...@@ -85,9 +85,10 @@ class _HistoryPointCashBackScreenState extends State<HistoryPointCashBackScreen> ...@@ -85,9 +85,10 @@ class _HistoryPointCashBackScreenState extends State<HistoryPointCashBackScreen>
return _buildVoucherItem(order); return _buildVoucherItem(order);
}, },
), ),
), );
), },
), ),
),
], ],
), ),
); );
......
import 'package:get/get_rx/src/rx_types/rx_types.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/extensions/collection_extension.dart'; import 'package:mypoint_flutter_app/core/utils/extensions/collection_extension.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart'; import 'package:mypoint_flutter_app/core/network/restful_api_client_all_request.dart';
import '../../networking/restful_api_viewmodel.dart'; import '../../core/network/restful_api_viewmodel.dart';
import 'models/history_point_cashback_model.dart'; import 'models/history_point_cashback_model.dart';
class HistoryPointCashBackViewModel extends RestfulApiViewModel { class HistoryPointCashBackViewModel extends RestfulApiViewModel {
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/extensions/datetime_extensions.dart'; import 'package:mypoint_flutter_app/core/utils/extensions/datetime_extensions.dart';
import '../../../extensions/date_format.dart';
class HistoryPointCashBackResponse { class HistoryPointCashBackResponse {
final double points; final double points;
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../widgets/image_loader.dart'; import '../../../shared/widgets/image_loader.dart';
import '../../voucher/sub_widget/voucher_section_title.dart'; import '../../voucher/sub_widget/voucher_section_title.dart';
import '../models/achievement_model.dart'; import '../../achievement/model/achievement_model.dart';
import '../models/main_section_config_model.dart'; import '../models/main_section_config_model.dart';
class AchievementCarousel extends StatelessWidget { class AchievementCarousel extends StatelessWidget {
...@@ -33,7 +33,7 @@ class AchievementCarousel extends StatelessWidget { ...@@ -33,7 +33,7 @@ class AchievementCarousel extends StatelessWidget {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: items.length, itemCount: items.length,
separatorBuilder: (_, __) => const SizedBox(width: 12), separatorBuilder: (_, _) => const SizedBox(width: 12),
itemBuilder: (context, index) => AchievementCard( itemBuilder: (context, index) => AchievementCard(
achievement: items[index], achievement: items[index],
onTap: () => onTap?.call(items[index]), onTap: () => onTap?.call(items[index]),
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart'; import 'package:mypoint_flutter_app/core/utils/extensions/string_extension.dart';
import '../../affiliate/model/affiliate_brand_model.dart'; import '../../affiliate/model/affiliate_brand_model.dart';
import '../../voucher/sub_widget/voucher_section_title.dart'; import '../../voucher/sub_widget/voucher_section_title.dart';
import '../models/main_section_config_model.dart'; import '../models/main_section_config_model.dart';
...@@ -11,7 +11,7 @@ class AffiliateBrandGridWidget extends StatelessWidget { ...@@ -11,7 +11,7 @@ class AffiliateBrandGridWidget extends StatelessWidget {
const AffiliateBrandGridWidget({super.key, required this.affiliateBrands, this.sectionConfig, this.onTap}); const AffiliateBrandGridWidget({super.key, required this.affiliateBrands, this.sectionConfig, this.onTap});
_handleTapRightButton() { void _handleTapRightButton() {
sectionConfig?.buttonViewAll?.directionalScreen?.begin(); sectionConfig?.buttonViewAll?.directionalScreen?.begin();
} }
...@@ -74,7 +74,7 @@ class AffiliateBrandGridWidget extends StatelessWidget { ...@@ -74,7 +74,7 @@ class AffiliateBrandGridWidget extends StatelessWidget {
child: Image.network( child: Image.network(
brand.logo ?? '', brand.logo ?? '',
fit: BoxFit.contain, fit: BoxFit.contain,
errorBuilder: (_, __, ___) => const Icon(Icons.broken_image), errorBuilder: (_, _, _) => const Icon(Icons.broken_image),
), ),
), ),
), ),
......
...@@ -22,12 +22,16 @@ class _BannerCarouselState extends State<BannerCarousel> { ...@@ -22,12 +22,16 @@ class _BannerCarouselState extends State<BannerCarousel> {
Timer? _autoPlayTimer; Timer? _autoPlayTimer;
final bool _isDragging = false; final bool _isDragging = false;
bool _isUserScrolling = false; bool _isUserScrolling = false;
double? _firstBannerAspectRatio; // height / width
String? _aspectRatioSourceUrl;
static const double _defaultAspectRatio = 535 / 1125;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_controller = InfiniteScrollController(initialItem: 0); _controller = InfiniteScrollController(initialItem: 0);
_startAutoPlay(); _startAutoPlay();
_resolveFirstBannerAspectRatio();
} }
void _startAutoPlay() { void _startAutoPlay() {
...@@ -55,13 +59,57 @@ class _BannerCarouselState extends State<BannerCarousel> { ...@@ -55,13 +59,57 @@ class _BannerCarouselState extends State<BannerCarousel> {
super.dispose(); super.dispose();
} }
_handleTapRightButton() { @override
void didUpdateWidget(covariant BannerCarousel oldWidget) {
super.didUpdateWidget(oldWidget);
final oldUrl = oldWidget.banners.isNotEmpty ? oldWidget.banners.first.itemImage : null;
final newUrl = widget.banners.isNotEmpty ? widget.banners.first.itemImage : null;
if (oldUrl != newUrl) {
_firstBannerAspectRatio = null;
_aspectRatioSourceUrl = null;
_resolveFirstBannerAspectRatio();
}
}
void _resolveFirstBannerAspectRatio() {
if (widget.banners.isEmpty) return;
final url = widget.banners.first.itemImage;
if ((url ?? '').isEmpty || url == _aspectRatioSourceUrl) {
return;
}
_aspectRatioSourceUrl = url;
final provider = NetworkImage(url!);
final stream = provider.resolve(const ImageConfiguration());
late ImageStreamListener listener;
listener = ImageStreamListener(
(info, _) {
final ratio = info.image.height / info.image.width;
if (mounted) {
setState(() {
_firstBannerAspectRatio = ratio;
});
}
stream.removeListener(listener);
},
onError: (_, _) {
stream.removeListener(listener);
},
);
stream.addListener(listener);
}
void _handleTapRightButton() {
widget.sectionConfig?.buttonViewAll?.directionalScreen?.begin(); widget.sectionConfig?.buttonViewAll?.directionalScreen?.begin();
} }
double _currentBannerAspectRatio() {
return _firstBannerAspectRatio ?? _defaultAspectRatio;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width * 0.9; final width = MediaQuery.of(context).size.width * 0.9;
final height = width * _currentBannerAspectRatio() + 16;
return Column( return Column(
children: [ children: [
if ((widget.sectionConfig?.name ?? "").isNotEmpty) if ((widget.sectionConfig?.name ?? "").isNotEmpty)
...@@ -69,66 +117,74 @@ class _BannerCarouselState extends State<BannerCarousel> { ...@@ -69,66 +117,74 @@ class _BannerCarouselState extends State<BannerCarousel> {
title: widget.sectionConfig?.name ?? "", title: widget.sectionConfig?.name ?? "",
onViewAll: widget.sectionConfig?.buttonViewAll?.directionalScreen != null ? _handleTapRightButton : null, onViewAll: widget.sectionConfig?.buttonViewAll?.directionalScreen != null ? _handleTapRightButton : null,
), ),
GestureDetector( if (widget.banners.isNotEmpty)
onPanDown: (_) => _pauseAutoPlayTemporarily(), GestureDetector(
child: SizedBox( onPanDown: (_) => _pauseAutoPlayTemporarily(),
height: width * 135 / 343 + 16, child: SizedBox(
child: Stack( // height: width * 135 / 343 + 16,
alignment: Alignment.bottomCenter, height: height,
children: [ child: Stack(
InfiniteCarousel.builder( alignment: Alignment.bottomCenter,
itemCount: widget.banners.length, children: [
itemExtent: width, InfiniteCarousel.builder(
scrollBehavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), itemCount: widget.banners.length,
loop: true, itemExtent: width,
center: true, scrollBehavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
anchor: 0.0, loop: true,
velocityFactor: 0.1, // ✅ fix lỗi: snap từng page center: true,
controller: _controller, anchor: 0.0,
onIndexChanged: (index) => setState(() => _currentIndex = index % widget.banners.length), velocityFactor: 0.1,
itemBuilder: (context, itemIndex, realIndex) { controller: _controller,
return GestureDetector( onIndexChanged: (index) => setState(() => _currentIndex = index % widget.banners.length),
onTap: () => widget.onTap?.call(widget.banners[itemIndex % widget.banners.length]), itemBuilder: (context, itemIndex, realIndex) {
child: Padding( final bannerIndex = itemIndex % widget.banners.length;
padding: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 8.0), final ratio = _currentBannerAspectRatio();
child: ClipRRect( return GestureDetector(
borderRadius: BorderRadius.circular(12), onTap: () => widget.onTap?.call(widget.banners[bannerIndex]),
child: Image.network( child: Padding(
widget.banners[itemIndex % widget.banners.length].itemImage ?? "", padding: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 8.0),
fit: BoxFit.cover, child: AspectRatio(
width: double.infinity, aspectRatio: ratio <= 0 ? 1125 / 535 : 1 / ratio,
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
widget.banners[bannerIndex].itemImage ?? "",
fit: BoxFit.cover,
width: double.infinity,
),
),
), ),
), ),
), );
); },
}, ),
), Positioned(
Positioned( bottom: 12,
bottom: 12, child: Row(
child: Row( mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, children:
children: widget.banners.asMap().entries.map((entry) {
widget.banners.asMap().entries.map((entry) { return GestureDetector(
return GestureDetector( onTap:
onTap: () =>
() => _controller.animateToItem(entry.key, duration: const Duration(milliseconds: 500)), _controller.animateToItem(entry.key, duration: const Duration(milliseconds: 500)),
child: Container( child: Container(
width: 8.0, width: 8.0,
height: 8.0, height: 8.0,
margin: const EdgeInsets.symmetric(horizontal: 4.0), margin: const EdgeInsets.symmetric(horizontal: 4.0),
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: _currentIndex == entry.key ? Colors.white : Colors.white.withOpacity(0.4), color: _currentIndex == entry.key ? Colors.white : Colors.white.withOpacity(0.4),
),
), ),
), );
); }).toList(),
}).toList(), ),
), ),
), ],
], ),
), ),
), ),
),
], ],
); );
} }
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart'; import 'package:mypoint_flutter_app/shared/widgets/image_loader.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart';
import '../../voucher/sub_widget/voucher_section_title.dart'; import '../../voucher/sub_widget/voucher_section_title.dart';
import '../models/brand_model.dart'; import '../../affiliate_brand_detail/models/brand_model.dart';
import '../models/main_section_config_model.dart'; import '../models/main_section_config_model.dart';
class BrandGridWidget extends StatelessWidget { class BrandGridWidget extends StatelessWidget {
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/extensions/num_extension.dart'; import 'package:mypoint_flutter_app/core/utils/extensions/num_extension.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart'; import 'package:mypoint_flutter_app/shared/widgets/image_loader.dart';
import '../../voucher/models/product_model.dart'; import '../../voucher/models/product_model.dart';
import '../../voucher/sub_widget/voucher_section_title.dart';
import '../models/main_section_config_model.dart'; import '../models/main_section_config_model.dart';
import 'flash_sale_header_widget.dart'; import 'flash_sale_header_widget.dart';
......
...@@ -80,7 +80,6 @@ class _FlashSaleHeaderState extends State<FlashSaleHeader> { ...@@ -80,7 +80,6 @@ class _FlashSaleHeaderState extends State<FlashSaleHeader> {
} }
Widget _buildCountdownSection(Duration duration) { Widget _buildCountdownSection(Duration duration) {
final bool isCounting = duration.inSeconds > 0;
final label = (widget.flashSale?.desTime ?? 'Kết thúc trong'); final label = (widget.flashSale?.desTime ?? 'Kết thúc trong');
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/num_extension.dart'; import 'package:mypoint_flutter_app/core/utils/extensions/num_extension.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart'; import 'package:mypoint_flutter_app/shared/widgets/image_loader.dart';
import '../../../preference/data_preference.dart'; import '../../../shared/preferences/data_preference.dart';
import '../../../preference/point/header_home_model.dart'; import '../../../shared/preferences/point/point_manager.dart';
import '../../../preference/point/point_manager.dart';
import '../../../shared/router_gage.dart'; import '../../../shared/router_gage.dart';
import '../models/notification_unread_model.dart'; import '../models/header_home_model.dart';
import '../../notification/models/notification_unread_model.dart';
class HomeGreetingHeader extends StatelessWidget { class HomeGreetingHeader extends StatelessWidget {
final double? heightContent; final double? heightContent;
......
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart'; import 'package:mypoint_flutter_app/shared/widgets/image_loader.dart';
class HoverView extends StatefulWidget { class HoverView extends StatefulWidget {
final String imagePath; final String imagePath;
...@@ -31,7 +30,7 @@ class HoverView extends StatefulWidget { ...@@ -31,7 +30,7 @@ class HoverView extends StatefulWidget {
class _HoverViewState extends State<HoverView> { class _HoverViewState extends State<HoverView> {
Offset _position = const Offset(0, 0); Offset _position = const Offset(0, 0);
bool _isDragging = false; bool _isDragging = false;
bool _showCloseButton = false; // bool _showCloseButton = true;
Size _screenSize = Size.zero; Size _screenSize = Size.zero;
bool _isInitialized = false; bool _isInitialized = false;
final _remainingSeconds = 0.obs; final _remainingSeconds = 0.obs;
...@@ -89,7 +88,7 @@ class _HoverViewState extends State<HoverView> { ...@@ -89,7 +88,7 @@ class _HoverViewState extends State<HoverView> {
void _onPanStart(DragStartDetails details) { void _onPanStart(DragStartDetails details) {
setState(() { setState(() {
_isDragging = true; _isDragging = true;
_showCloseButton = true; // _showCloseButton = true;
}); });
} }
...@@ -148,7 +147,7 @@ class _HoverViewState extends State<HoverView> { ...@@ -148,7 +147,7 @@ class _HoverViewState extends State<HoverView> {
// Animate to nearest corner using setState // Animate to nearest corner using setState
setState(() { setState(() {
_position = nearestCorner; _position = nearestCorner;
_showCloseButton = false; // _showCloseButton = false;
}); });
} }
...@@ -158,6 +157,9 @@ class _HoverViewState extends State<HoverView> { ...@@ -158,6 +157,9 @@ class _HoverViewState extends State<HoverView> {
} }
} }
void _onLongPress() {
}
void _onClose() { void _onClose() {
if (widget.onClose != null) { if (widget.onClose != null) {
widget.onClose!(); widget.onClose!();
...@@ -196,11 +198,7 @@ class _HoverViewState extends State<HoverView> { ...@@ -196,11 +198,7 @@ class _HoverViewState extends State<HoverView> {
onPanUpdate: _onPanUpdate, onPanUpdate: _onPanUpdate,
onPanEnd: _onPanEnd, onPanEnd: _onPanEnd,
onTap: _onTap, onTap: _onTap,
onLongPress: () { onLongPress: _onLongPress,
setState(() {
_showCloseButton = !_showCloseButton;
});
},
child: Stack( child: Stack(
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ children: [
...@@ -208,7 +206,7 @@ class _HoverViewState extends State<HoverView> { ...@@ -208,7 +206,7 @@ class _HoverViewState extends State<HoverView> {
Column( Column(
children: [ children: [
AnimatedScale( AnimatedScale(
scale: _isDragging ? 1.1 : 1.0, scale: _isDragging ? 1.2 : 1.0,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
child: SizedBox( child: SizedBox(
width: widget.size, width: widget.size,
...@@ -237,7 +235,7 @@ class _HoverViewState extends State<HoverView> { ...@@ -237,7 +235,7 @@ class _HoverViewState extends State<HoverView> {
], ],
), ),
// Close button // Close button
if (_showCloseButton) // if (_showCloseButton)
Positioned( Positioned(
top: -8, top: -8,
right: -8, right: -8,
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../widgets/image_loader.dart'; import '../../../shared/widgets/image_loader.dart';
import '../../voucher/sub_widget/voucher_section_title.dart'; import '../../voucher/sub_widget/voucher_section_title.dart';
import '../models/main_section_config_model.dart'; import '../models/main_section_config_model.dart';
import '../models/main_service_model.dart'; import '../models/main_service_model.dart';
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart'; import 'package:mypoint_flutter_app/shared/widgets/image_loader.dart';
import '../../voucher/sub_widget/voucher_section_title.dart'; import '../../voucher/sub_widget/voucher_section_title.dart';
import '../models/main_section_config_model.dart'; import '../models/main_section_config_model.dart';
import '../models/my_product_model.dart'; import '../../voucher/models/my_product_model.dart';
class MyProductCarouselWidget extends StatelessWidget { class MyProductCarouselWidget extends StatelessWidget {
final List<MyProductModel> items; final List<MyProductModel> items;
...@@ -33,7 +33,7 @@ class MyProductCarouselWidget extends StatelessWidget { ...@@ -33,7 +33,7 @@ class MyProductCarouselWidget extends StatelessWidget {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: items.length, itemCount: items.length,
separatorBuilder: (_, __) => const SizedBox(width: 12), separatorBuilder: (_, _) => const SizedBox(width: 12),
itemBuilder: (context, index) => _buildItem(context, items[index], widthItem), itemBuilder: (context, index) => _buildItem(context, items[index], widthItem),
), ),
), ),
...@@ -88,7 +88,7 @@ class MyProductCarouselWidget extends StatelessWidget { ...@@ -88,7 +88,7 @@ class MyProductCarouselWidget extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text('HSD: ${product.expire ?? ''}', style: const TextStyle(fontSize: 13, color: Colors.black54)), Text('HSD: ${product.expire}', style: const TextStyle(fontSize: 13, color: Colors.black54)),
], ],
), ),
), ),
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart'; import 'package:mypoint_flutter_app/shared/widgets/image_loader.dart';
import '../../faqs/faqs_model.dart'; import '../../faqs/faqs_model.dart';
import '../../voucher/sub_widget/voucher_section_title.dart'; import '../../voucher/sub_widget/voucher_section_title.dart';
import '../models/main_section_config_model.dart'; import '../models/main_section_config_model.dart';
...@@ -32,7 +31,7 @@ class NewsCarouselWidget extends StatelessWidget { ...@@ -32,7 +31,7 @@ class NewsCarouselWidget extends StatelessWidget {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: items.length, itemCount: items.length,
separatorBuilder: (_, __) => const SizedBox(width: 12), separatorBuilder: (_, _) => const SizedBox(width: 12),
itemBuilder: (context, index) => _buildItem(context, items[index]), itemBuilder: (context, index) => _buildItem(context, items[index]),
), ),
), ),
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart'; import 'package:mypoint_flutter_app/shared/widgets/image_loader.dart';
import '../../../widgets/custom_point_text_tag.dart'; import '../../../shared/widgets/custom_point_text_tag.dart';
import '../../voucher/models/product_model.dart'; import '../../voucher/models/product_model.dart';
import '../../voucher/sub_widget/voucher_section_title.dart'; import '../../voucher/sub_widget/voucher_section_title.dart';
import '../models/main_section_config_model.dart'; import '../models/main_section_config_model.dart';
...@@ -13,7 +13,7 @@ class ProductGrid extends StatelessWidget { ...@@ -13,7 +13,7 @@ class ProductGrid extends StatelessWidget {
const ProductGrid({super.key, required this.products, this.sectionConfig, this.onTap}); const ProductGrid({super.key, required this.products, this.sectionConfig, this.onTap});
_handleTapRightButton() { void _handleTapRightButton() {
sectionConfig?.buttonViewAll?.directionalScreen?.begin(); sectionConfig?.buttonViewAll?.directionalScreen?.begin();
} }
......
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/api/notification_api.dart' import 'package:mypoint_flutter_app/core/network/api/notification_api.dart' deferred as notification_api;
deferred as notification_api; import 'package:mypoint_flutter_app/core/network/restful_api_client_all_request.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart'; import 'package:mypoint_flutter_app/shared/preferences/data_preference.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart'; import '../../core/network/restful_api_viewmodel.dart';
import '../../networking/restful_api_viewmodel.dart'; import '../../shared/preferences/point/point_manager.dart';
import '../../preference/point/point_manager.dart'; import 'models/header_home_model.dart';
import '../../preference/point/header_home_model.dart'; import '../notification/models/notification_unread_model.dart';
import 'models/notification_unread_model.dart';
class HeaderHomeRepository extends RestfulApiViewModel { class HeaderHomeRepository extends RestfulApiViewModel {
HeaderHomeRepository._(); HeaderHomeRepository._();
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/preference/point/point_manager.dart'; import 'package:mypoint_flutter_app/features/home/custom_widget/header_home_widget.dart';
import 'package:mypoint_flutter_app/screen/home/custom_widget/header_home_widget.dart'; import 'package:mypoint_flutter_app/features/home/custom_widget/product_grid_widget.dart';
import 'package:mypoint_flutter_app/screen/home/custom_widget/product_grid_widget.dart'; import 'package:mypoint_flutter_app/features/pipi/pipi_detail_screen.dart';
import 'package:mypoint_flutter_app/screen/pipi/pipi_detail_screen.dart'; import 'package:mypoint_flutter_app/features/voucher/models/product_model.dart';
import 'package:mypoint_flutter_app/screen/voucher/models/product_model.dart';
import 'package:mypoint_flutter_app/shared/router_gage.dart'; import 'package:mypoint_flutter_app/shared/router_gage.dart';
import '../../directional/directional_action_type.dart'; import '../../shared/widgets/base_view/base_screen.dart';
import '../../widgets/custom_empty_widget.dart'; import '../../shared/widgets/base_view/basic_state.dart';
import '../../app/routing/directional_action_type.dart';
import '../../shared/widgets/custom_empty_widget.dart';
import '../popup_manager/popup_runner_helper.dart'; import '../popup_manager/popup_runner_helper.dart';
import 'custom_widget/achievement_carousel_widget.dart'; import 'custom_widget/achievement_carousel_widget.dart';
import 'custom_widget/affiliate_brand_grid_widget.dart'; import 'custom_widget/affiliate_brand_grid_widget.dart';
...@@ -21,25 +22,29 @@ import 'custom_widget/news_carousel_widget.dart'; ...@@ -21,25 +22,29 @@ import 'custom_widget/news_carousel_widget.dart';
import 'header_home_viewmodel.dart'; import 'header_home_viewmodel.dart';
import 'home_tab_viewmodel.dart'; import 'home_tab_viewmodel.dart';
import 'models/header_section_type.dart'; import 'models/header_section_type.dart';
import 'models/hover_data_model.dart';
import 'models/main_section_config_model.dart'; import 'models/main_section_config_model.dart';
class HomeScreen extends StatefulWidget { class HomeScreen extends BaseScreen {
const HomeScreen({super.key}); const HomeScreen({super.key});
@override @override
State<HomeScreen> createState() => _HomeScreenState(); State<HomeScreen> createState() => _HomeScreenState();
} }
class _HomeScreenState extends State<HomeScreen> with PopupOnInit { class _HomeScreenState extends BaseState<HomeScreen> with PopupOnInit, BasicState {
final HomeTabViewModel _viewModel = Get.put(HomeTabViewModel()); final HomeTabViewModel _viewModel = Get.put(HomeTabViewModel());
final _headerHomeVM = Get.find<HeaderHomeViewModel>(); final _headerHomeVM = Get.find<HeaderHomeViewModel>();
final RxBool _showHover = true.obs; final RxBool _showHover = false.obs;
late final Worker _hoverDataWorker;
bool _isRefreshingFlashSale = false; bool _isRefreshingFlashSale = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
UserPointManager().fetchUserPoint(); debugPrint('HomeScreen initState');
// UserPointManager().fetchUserPoint();
_hoverDataWorker = ever<HoverDataModel?>(_viewModel.hoverData, _onHoverDataChanged);
_headerHomeVM.freshData(); _headerHomeVM.freshData();
runPopupCheck(DirectionalScreenName.home); runPopupCheck(DirectionalScreenName.home);
} }
...@@ -70,7 +75,12 @@ class _HomeScreenState extends State<HomeScreen> with PopupOnInit { ...@@ -70,7 +75,12 @@ class _HomeScreenState extends State<HomeScreen> with PopupOnInit {
List<Widget> _buildSectionContent() { List<Widget> _buildSectionContent() {
final sections = _viewModel.sectionLayouts.map(_buildSection).whereType<Widget>().toList(); final sections = _viewModel.sectionLayouts.map(_buildSection).whereType<Widget>().toList();
if (sections.isEmpty) { if (sections.isEmpty) {
return const [Padding(padding: EdgeInsets.symmetric(vertical: 40), child: EmptyWidget())]; return [
Padding(
padding: const EdgeInsets.symmetric(vertical: 40),
child: EmptyWidget(isLoading: _viewModel.isLoading.value),
),
];
} }
return sections; return sections;
} }
...@@ -153,7 +163,7 @@ class _HomeScreenState extends State<HomeScreen> with PopupOnInit { ...@@ -153,7 +163,7 @@ class _HomeScreenState extends State<HomeScreen> with PopupOnInit {
} }
@override @override
Widget build(BuildContext context) { Widget createBody() {
final paddingBottom = MediaQuery.of(context).padding.bottom + 20; final paddingBottom = MediaQuery.of(context).padding.bottom + 20;
final width = MediaQuery.of(context).size.width; final width = MediaQuery.of(context).size.width;
final heightHeader = width * 86 / 375 + 112; final heightHeader = width * 86 / 375 + 112;
...@@ -203,14 +213,66 @@ class _HomeScreenState extends State<HomeScreen> with PopupOnInit { ...@@ -203,14 +213,66 @@ class _HomeScreenState extends State<HomeScreen> with PopupOnInit {
} }
void _handleCloseHoverView() { void _handleCloseHoverView() {
setState(() { _handleCloseHoverViewInternal();
}
void _handleCloseHoverViewInternal({bool dismissedByUser = true}) {
if (dismissedByUser) {
_viewModel.hoverDismissed = true;
}
if (mounted) {
_showHover.value = false; _showHover.value = false;
}); }
} }
Future<void> _onRefresh() async { Future<void> _onRefresh() async {
_resetHoverViewState();
await _viewModel.getSectionLayoutHome(showLoading: false); await _viewModel.getSectionLayoutHome(showLoading: false);
await _viewModel.loadDataPiPiHome(); await _viewModel.loadDataPiPiHome();
await _headerHomeVM.freshData(); await _headerHomeVM.freshData();
} }
@override
void onDispose() {
_hoverDataWorker.dispose();
_pauseCountdown();
super.onDispose();
}
@override
void onRouteWillDisappear() {
super.onRouteWillDisappear();
_pauseCountdown();
}
@override
void onRouteDidAppear() {
super.onRouteDidAppear();
if (_viewModel.hoverDismissed) return;
_viewModel.loadDataPiPiHome();
}
void _pauseCountdown() {
_handleCloseHoverViewInternal(dismissedByUser: false);
}
void _resetHoverViewState() {
_viewModel.hoverDismissed = false;
_updateHoverVisibility();
}
void _updateHoverVisibility() {
if (_viewModel.hoverDismissed || !mounted) return;
_showHover.value = _shouldShowHover(_viewModel.hoverData.value);
}
void _onHoverDataChanged(HoverDataModel? data) {
if (_viewModel.hoverDismissed || !mounted) return;
_showHover.value = _shouldShowHover(data);
}
bool _shouldShowHover(HoverDataModel? data) {
if (data == null) return false;
return data.icon?.isNotEmpty == true;
}
} }
...@@ -2,20 +2,20 @@ import 'dart:convert'; ...@@ -2,20 +2,20 @@ import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart'; import 'package:mypoint_flutter_app/core/network/restful_api_client_all_request.dart';
import '../../networking/restful_api_viewmodel.dart'; import '../../core/network/restful_api_viewmodel.dart';
import '../affiliate/model/affiliate_brand_model.dart'; import '../affiliate/model/affiliate_brand_model.dart';
import '../faqs/faqs_model.dart'; import '../faqs/faqs_model.dart';
import '../voucher/models/product_model.dart'; import '../voucher/models/product_model.dart';
import 'models/achievement_model.dart'; import '../achievement/model/achievement_model.dart';
import 'models/banner_model.dart'; import 'models/banner_model.dart';
import 'models/brand_model.dart'; import '../affiliate_brand_detail/models/brand_model.dart';
import 'models/flash_sale_model.dart'; import '../flash_sale/models/flash_sale_model.dart';
import 'models/header_section_type.dart'; import 'models/header_section_type.dart';
import 'models/hover_data_model.dart'; import 'models/hover_data_model.dart';
import 'models/main_section_config_model.dart'; import 'models/main_section_config_model.dart';
import 'models/main_service_model.dart'; import 'models/main_service_model.dart';
import 'models/my_product_model.dart'; import '../voucher/models/my_product_model.dart';
class HomeTabViewModel extends RestfulApiViewModel { class HomeTabViewModel extends RestfulApiViewModel {
final RxList<ProductModel> products = <ProductModel>[].obs; final RxList<ProductModel> products = <ProductModel>[].obs;
...@@ -29,6 +29,7 @@ class HomeTabViewModel extends RestfulApiViewModel { ...@@ -29,6 +29,7 @@ class HomeTabViewModel extends RestfulApiViewModel {
final RxList<MainSectionConfigModel> sectionLayouts = <MainSectionConfigModel>[].obs; final RxList<MainSectionConfigModel> sectionLayouts = <MainSectionConfigModel>[].obs;
final Rxn<FlashSaleModel> flashSaleData = Rxn<FlashSaleModel>(); final Rxn<FlashSaleModel> flashSaleData = Rxn<FlashSaleModel>();
final Rxn<HoverDataModel> hoverData = Rxn<HoverDataModel>(); final Rxn<HoverDataModel> hoverData = Rxn<HoverDataModel>();
bool hoverDismissed = false;
late final Map<HeaderSectionType, Future<void> Function(MainSectionConfigModel)> _sectionLoaders; late final Map<HeaderSectionType, Future<void> Function(MainSectionConfigModel)> _sectionLoaders;
...@@ -47,7 +48,6 @@ class HomeTabViewModel extends RestfulApiViewModel { ...@@ -47,7 +48,6 @@ class HomeTabViewModel extends RestfulApiViewModel {
HeaderSectionType.myProduct: _loadMyProducts, HeaderSectionType.myProduct: _loadMyProducts,
}; };
getSectionLayoutHome(); getSectionLayoutHome();
loadDataPiPiHome();
} }
MainSectionConfigModel? getMainSectionConfigModel(HeaderSectionType type) { MainSectionConfigModel? getMainSectionConfigModel(HeaderSectionType type) {
......
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:mypoint_flutter_app/directional/directional_screen.dart'; import 'package:mypoint_flutter_app/shared/navigation/directional_screen.dart';
part 'banner_model.g.dart'; part 'banner_model.g.dart';
@JsonSerializable() @JsonSerializable()
......
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