/* This is home page we used AutomaticKeepAliveClientMixin to keep the state when moving from 1 navbar to another navbar, so the page is not refresh overtime */ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:badges/badges.dart' as badges; import 'package:mobdr/main.dart'; import 'package:mobdr/config/constant.dart'; import 'package:mobdr/cubit/language/language_cubit.dart'; import 'package:mobdr/cubit/language/app_localizations.dart'; import 'package:mobdr/service/shared_prefs.dart'; import 'package:mobdr/config/global_style.dart'; import 'package:mobdr/events.dart'; import 'package:mobdr/model/visit_model.dart'; import 'package:mobdr/ui/visit/visit_photo_typology.dart'; import 'package:mobdr/ui/general/notification.dart'; import 'package:mobdr/ui/reusable/reusable_widget.dart'; import 'package:mobdr/ui/reusable/cache_image_network.dart'; class TabHomePage extends StatefulWidget { final Function(int) navigateToTab; TabHomePage({required this.navigateToTab}); @override _TabHomePageState createState() => _TabHomePageState(); } class _TabHomePageState extends State with AutomaticKeepAliveClientMixin { // initialize global function and reusable widget final _reusableWidget = ReusableWidget(); late StreamSubscription subVisitPhotoCountEvent; late StreamSubscription subRefreshCalendarEvent; // keep the state to do not refresh when switch navbar @override bool get wantKeepAlive => true; String defaultLang = 'en'; DateTime _todayDate = DateTime.now(); late LanguageCubit _languageCubit; late List todayVisitData = []; late List previousVisitData = []; VisitModel? lastVisitData = null; @override void initState() { super.initState(); _languageCubit = BlocProvider.of(context); _getLocale().then((val) { setState(() { defaultLang = val!; }); }); loadData().then((_) { setState(() { // Listen refresh photo count event subVisitPhotoCountEvent = eventBus.on().listen((e) { SharedPrefs().last_id_visite = e.id_visite; setState(() { for (int i = 0; i < todayVisitData.length; i++) { if (todayVisitData[i].id_visite == e.id_visite) { todayVisitData[i].photoCount = e.photoCount; lastVisitData = todayVisitData[i]; break; } } for (int i = 0; i < previousVisitData.length; i++) { if (previousVisitData[i].id_visite == e.id_visite) { previousVisitData[i].photoCount = e.photoCount; lastVisitData = previousVisitData[i]; break; } } }); }); }); // Listen refresh calendar event subRefreshCalendarEvent = eventBus.on().listen((e) { setState(() { loadData(); }); }); }); } @override void dispose() { subRefreshCalendarEvent.cancel(); subVisitPhotoCountEvent.cancel(); super.dispose(); } Future _getLocale() async { final SharedPreferences _pref = await SharedPreferences.getInstance(); String? lCode = _pref.getString('lCode'); return lCode; } void changeLocale(Locale locale) async { final SharedPreferences _pref = await SharedPreferences.getInstance(); await _pref.setString('lCode', locale.languageCode); await _pref.setString('cCode', locale.countryCode!); _languageCubit.changeLanguage(locale); defaultLang = locale.languageCode; } @override Widget build(BuildContext context) { // if we used AutomaticKeepAliveClientMixin, we must call super.build(context); super.build(context); final double boxImageSize = (MediaQuery.of(context).size.width / 3); final formattedDate = DateFormat('EEEE d MMMM y', 'fr_FR').format(_todayDate); return Scaffold( appBar: AppBar( automaticallyImplyLeading: false, elevation: GlobalStyle.appBarElevation, title: Align( alignment: Alignment.centerLeft, child: Text( AppLocalizations.of(context)!.translate('i18n_hello')! + ', ${SharedPrefs().prenom}', style: GlobalStyle.appBarTitle, ), ), backgroundColor: GlobalStyle.appBarBackgroundColor, systemOverlayStyle: GlobalStyle.appBarSystemOverlayStyle, actions: [ IconButton( icon: _reusableWidget.customNotifIcon( count: 0, notifColor: BLACK_GREY, ), onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => NotificationPage(), ), ); }, ), ], ), body: ListView( physics: AlwaysScrollableScrollPhysics(), children: [ Padding( padding: EdgeInsets.all(16.0), child: Row( children: [ Icon(Icons.calendar_today), SizedBox(width: 8.0), Text( formattedDate, style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold), ), ], ), ), _buildLastVisit(boxImageSize), _buildTodayVisits(boxImageSize), _builPreviousVisits(boxImageSize), ], ), ); } Widget _buildLastVisit(boxImageSize) { if (lastVisitData == null) { return SizedBox.shrink(); // Rien ne sera affiché } else { return Column( children: [ Container( padding: EdgeInsets.fromLTRB(16, 8, 0, 0), child: Row( children: [ Text('Last visit access', style: GlobalStyle.horizontalTitle), ], ), ), Container( margin: EdgeInsets.only(top: 8), alignment: Alignment.centerLeft, padding: EdgeInsets.symmetric(horizontal: 12), child: buildVisitListCard(context, lastVisitData!)), ], ); } } Widget _buildTodayVisits(boxImageSize) { return Column( children: [ Container( padding: EdgeInsets.fromLTRB(16, 16, 16, 0), child: Row( children: [ Text('Today Visits', style: GlobalStyle.horizontalTitle), Container( child: Padding( padding: const EdgeInsets.symmetric( vertical: 2.0, horizontal: 8.0), child: badges.Badge( badgeStyle: badges.BadgeStyle( badgeColor: Colors.black, padding: EdgeInsets.all(todayVisitData.length >= 10 ? 2 : 6), ), badgeContent: Text( todayVisitData.length.toString(), style: TextStyle(color: Colors.white), ), ), ), ), ], ), ), if (todayVisitData.length == 0) Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TextButton( onPressed: () { widget.navigateToTab(1); // switch to synchronization page }, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Synchronize', style: TextStyle( color: Colors.blue, fontSize: 16, fontWeight: FontWeight.bold, ), ), Icon(Icons.chevron_right, color: Colors.blue), ], ), ), ], ) else Container( margin: EdgeInsets.only(top: 8), height: boxImageSize * GlobalStyle.cardHeightMultiplication, child: ListView.builder( padding: EdgeInsets.symmetric(horizontal: 12), scrollDirection: Axis.horizontal, itemCount: todayVisitData.length, itemBuilder: (BuildContext context, int index) { return buildHorizontalVisitListCard( context, todayVisitData[index]); }, ), ), ], ); } Widget _builPreviousVisits(boxImageSize) { if (previousVisitData.length == 0) { return SizedBox.shrink(); // Rien ne sera affiché } return Column( children: [ Container( padding: EdgeInsets.fromLTRB(16, 16, 16, 0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Previous Visits', style: GlobalStyle.horizontalTitle), ], ), ), Container( margin: EdgeInsets.only(top: 8), height: boxImageSize * GlobalStyle.cardHeightMultiplication, child: ListView.builder( padding: EdgeInsets.symmetric(horizontal: 12), scrollDirection: Axis.horizontal, itemCount: previousVisitData.length, itemBuilder: (BuildContext context, int index) { return buildHorizontalVisitListCard( context, previousVisitData[index]); }, ), ), ], ); } Widget buildVisitListCard(BuildContext context, VisitModel data) { final double boxImageSize = (MediaQuery.of(context).size.width / 4); return GestureDetector( onTap: () { Route route = MaterialPageRoute( builder: (context) => VisitPhotoTypologyPage(pp_visitModel: data), ); Navigator.push(context, route); }, child: Container( margin: EdgeInsets.fromLTRB(0, 0, 0, 0), child: Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), elevation: 2, color: Colors.white, child: Container( margin: EdgeInsets.all(8), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ ClipRRect( borderRadius: BorderRadius.all(Radius.circular(10)), child: buildCacheNetworkImage( width: boxImageSize, height: boxImageSize, url: data.image, ), ), SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( margin: EdgeInsets.only(top: 5), child: Row( children: [ Text(data.name, style: GlobalStyle.productPrice), ], ), ), Container(height: 8), Text( data.date, style: GlobalStyle.productSale, ), Container( margin: EdgeInsets.only(top: 5), child: Text( '${data.photoCount} Photo(s)', style: GlobalStyle.productPrice, ), ), Container(height: 8), Container( margin: EdgeInsets.only(top: 5), child: Row( children: [ Icon( Icons.store, color: SOFT_GREY, size: 20, ), Text( ' ' + data.type_visite, style: GlobalStyle.productName.copyWith( fontSize: 13, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), ], ), ), ], ), ], ), ), ), ), ); } Widget buildHorizontalVisitListCard(context, VisitModel data) { final double imageWidth = (MediaQuery.of(context).size.width / 2.3); final double imageheight = (MediaQuery.of(context).size.width / 3.07); return Container( width: imageWidth, child: Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), elevation: 2, color: Colors.white, child: GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { Route route = MaterialPageRoute( builder: (context) => VisitPhotoTypologyPage(pp_visitModel: data), ); Navigator.push(context, route); }, child: Stack( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ClipRRect( borderRadius: BorderRadius.only( topLeft: Radius.circular(10), topRight: Radius.circular(6)), child: buildCacheNetworkImage( width: imageWidth, height: imageheight, url: data.image)), Container( margin: EdgeInsets.all(8), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 36, child: Text(data.name, style: GlobalStyle.cardTitle, maxLines: 2, overflow: TextOverflow.ellipsis), ), ], ), ), ], ), Positioned( bottom: 4, right: 4, // alignement à droite child: GestureDetector( onTap: () { if (data.date_validation == null) { SharedPrefs().urlMP4 = '${ApiConstants.baseUrl}/MobilePortal4/index.html#ajax/visite_modification.html?visite=${data.id_visite}'; } else { SharedPrefs().urlMP4 = '${ApiConstants.baseUrl}/MobilePortal4/index.html#ajax/visite.html?visite=${data.id_visite}'; } eventBus.fire(UrlEvent(SharedPrefs().urlMP4)); }, child: Image.asset( "assets/images/logo_mp4.png", width: 24, height: 24, ), ), ), if (data.photoCount != 0) Positioned( bottom: 0, left: 2, // alignement à gauche child: Container( margin: EdgeInsets.symmetric(vertical: 2), child: badges.Badge( badgeStyle: badges.BadgeStyle( badgeColor: Colors.blue, padding: EdgeInsets.all(data.photoCount >= 10 ? 2 : 6), ), badgeContent: Text(data.photoCount.toString(), style: TextStyle(color: Colors.white)), child: Icon( Icons.camera_alt_sharp, color: Colors.grey, ), ), ), ), ], ), ), ), ); } /// Initializes data when the page loads. Future loadData() async { // visite model initialisation todayVisitData = await VisitModel.getTodayVisits(); previousVisitData = await VisitModel.getPreviousVisits(); // Search for the visit matching last_id_visit int lastIdVisite = SharedPrefs().last_id_visite; lastVisitData = null; if (lastIdVisite > 0) { for (var visit in todayVisitData) { if (visit.id_visite == lastIdVisite) { lastVisitData = visit; break; } } for (var visit in previousVisitData) { if (visit.id_visite == lastIdVisite) { lastVisitData = visit; break; } } if (lastVisitData == null) { SharedPrefs().last_id_visite = 0; } } } }