import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/modules/alerts/alerts_list_page.dart'; import 'package:test_sa/modules/asset_tracking/asset_tracking_provider.dart'; import 'package:test_sa/modules/asset_tracking/asset_tracking_search_screen.dart'; import 'package:test_sa/modules/asset_tracking/models/asset_tracking_search_model.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/views/widgets/loaders/app_loading.dart'; import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; import 'asset_tracking_item_view.dart'; import 'map_tracking_page.dart'; class AssetTrackingPage extends StatefulWidget { AssetTrackingPage({Key? key}) : super(key: key); @override _AssetTrackingPageState createState() { return _AssetTrackingPageState(); } } class _AssetTrackingPageState extends State with SingleTickerProviderStateMixin { int selectedIndex = 1; late AnimationController _animationController; late AssetTrackingProvider assetTrackingProvider; AssetTrackingSearchModel? _searchModel; @override void initState() { super.initState(); assetTrackingProvider = Provider.of(context, listen: false); _animationController = AnimationController( duration: const Duration(milliseconds: 1200), vsync: this, ); _animationController.forward(); WidgetsBinding.instance.addPostFrameCallback((_) { assetTrackingProvider.reset(); getAssetsFromPenguin(); }); } Future getAssetsFromPenguin({bool loadMore = false, AssetTrackingSearchModel? searchModel}) async { await assetTrackingProvider.getAssetsFromPenguin(loadMore: loadMore, searchModel: searchModel ?? _searchModel); } @override Widget build(BuildContext context) { return Scaffold( appBar: DefaultAppBar( title: "Live Asset Tracking", actions: [ if (selectedIndex != 3) IconButton( icon: Icon(Icons.search, color: AppColor.icon2Color(context)), padding: EdgeInsets.zero, iconSize: 20, visualDensity: VisualDensity.compact, tooltip: "Search", onPressed: () async { final result = await Navigator.push( context, CupertinoPageRoute( fullscreenDialog: true, builder: (context) => AssetTrackingSearchScreen( searchModel: _searchModel, ), ), ); if (result != null) { assetTrackingProvider.reset(); setState(() { _searchModel = result; }); await assetTrackingProvider.getAssetsFromPenguin( searchModel: result, ); } }, ), ], ), body: Column( // crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ if (_filterIsNotEmpty()) _buildActiveFiltersChips(), SizedBox( width: double.infinity, child: SegmentedButton( multiSelectionEnabled: false, emptySelectionAllowed: false, expandedInsets: const EdgeInsets.only(left: 16, top: 8, right: 16, bottom: 8), style: SegmentedButton.styleFrom( side: BorderSide(color: AppColor.borderColor(context)), textStyle: AppTextStyles.bodyText, backgroundColor: AppColor.background(context), foregroundColor: context.isDark ? AppColor.neutral30 : AppColor.black20, selectedForegroundColor: AppColor.addItemButtonColor(context), selectedBackgroundColor: AppColor.primary10, ), segments: >[ ButtonSegment( value: 1, label: const Text("List"), icon: Icon(Icons.list_rounded, color: AppColor.icon2Color(context)), ), ButtonSegment( value: 2, label: const Text("Grid"), icon: Icon(Icons.grid_view_rounded, color: AppColor.icon2Color(context)), ), ButtonSegment( value: 3, label: const Text("Map"), icon: Icon(Icons.map, color: AppColor.icon2Color(context)), ), ButtonSegment( value: 4, label: const Text("Alerts"), icon: Icon(Icons.warning_amber_rounded, color: AppColor.icon2Color(context)), ), ], selected: {selectedIndex}, onSelectionChanged: (Set newSelection) { setState(() { selectedIndex = newSelection.first; }); }, ), ), SafeArea( child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: selectedIndex == 1 ? assetsListView() : selectedIndex == 2 ? assetsGridView() : selectedIndex == 3 ? MapTrackingPage() : AlertsListPage() // selectedIndex == 3 ? MapTrackingPage() : assetsListView(), ), ).expanded, ], ), ); } Widget assetsListView() { return Consumer(builder: (context, provider, _) { return (provider.assetsList.isEmpty && !provider.isLoading) ? NoDataFound(message: context.translation.noDataFound).center : NotificationListener( onNotification: (scrollInfo) { if (!provider.isNextPageLoading && provider.nextPage && scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) { getAssetsFromPenguin(loadMore: true); } return false; }, child: ListView.separated( key: const ValueKey('asset-list_view'), padding: const EdgeInsets.all(16), itemCount: provider.isLoading ? 6 : provider.assetsList.length + (provider.isNextPageLoading ? 1 : 0), separatorBuilder: (listContext, itemIndex) => 12.height, itemBuilder: (listContext, itemIndex) { if (provider.isLoading) return const SizedBox().toAssetListShimmer(listContext, provider.isLoading); if (itemIndex >= provider.assetsList.length) { return const Padding( padding: EdgeInsets.symmetric(vertical: 16), child: ALoading(), ); } final delay = itemIndex * 0.08; final animation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _animationController, curve: Interval( delay.clamp(0.0, 0.7), (delay + 0.4).clamp(0.0, 1.0), curve: Curves.easeOut, ), ), ); final slideAnimation = Tween( begin: const Offset(0.0, 0.15), end: Offset.zero, ).animate( CurvedAnimation( parent: _animationController, curve: Interval( delay.clamp(0.0, 0.7), (delay + 0.4).clamp(0.0, 1.0), curve: Curves.easeOutCubic, ), ), ); return FadeTransition( opacity: animation, child: SlideTransition(position: slideAnimation, child: AssetTrackingListItemView(provider.assetsList[itemIndex], false)).onPress(() { Navigator.push(context, MaterialPageRoute(builder: (context) => MapTrackingPage(assetTrackingData: provider.assetsList[itemIndex]))); }), ); }, ), ); }); } Widget assetsGridView() { return Consumer(builder: (context, provider, _) { return (provider.assetsList.isEmpty && !provider.isLoading) ? NoDataFound(message: context.translation.noDataFound).center : NotificationListener( onNotification: (scrollInfo) { if (!provider.isLoading && !provider.isNextPageLoading && provider.nextPage && scrollInfo.metrics.pixels >= (scrollInfo.metrics.maxScrollExtent - 200)) { getAssetsFromPenguin(loadMore: true); } return false; }, child: Column( children: [ Expanded( child: GridView.builder( key: const ValueKey('asset-grid_view'), padding: const EdgeInsets.all(16), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: 0.9, // need to fix this it's not flexiable with content.. crossAxisSpacing: 12, mainAxisSpacing: 12, ), itemCount: provider.isLoading ? 6 : provider.assetsList.length, itemBuilder: (listContext, itemIndex) { if (provider.isLoading) return const SizedBox().toAssetListShimmer(listContext, provider.isLoading); final delay = itemIndex * 0.08; final animation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _animationController, curve: Interval( delay.clamp(0.0, 0.7), (delay + 0.4).clamp(0.0, 1.0), curve: Curves.easeOut, ), ), ); final slideAnimation = Tween( begin: const Offset(0.0, 0.15), end: Offset.zero, ).animate( CurvedAnimation( parent: _animationController, curve: Interval( delay.clamp(0.0, 0.7), (delay + 0.4).clamp(0.0, 1.0), curve: Curves.easeOutCubic, ), ), ); return FadeTransition( opacity: animation, child: SlideTransition(position: slideAnimation, child: AssetTrackingListItemView(provider.assetsList[itemIndex], true)).onPress(() { Navigator.push(context, MaterialPageRoute(builder: (context) => MapTrackingPage(assetTrackingData: provider.assetsList[itemIndex]))); }), ); }, ), ), if (provider.isNextPageLoading) const Padding( padding: EdgeInsets.symmetric(vertical: 16), child: ALoading(), ), ], ), ); }); } Widget _buildActiveFiltersChips() { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), decoration: BoxDecoration( color: AppColor.background(context), border: Border( bottom: BorderSide(color: AppColor.borderColor(context), width: 1), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: 4, children: [ Text( 'Active Filters', style: AppTextStyles.tinyFont2.copyWith(color: AppColor.labelTextStyleColor(context), fontWeight: FontWeight.w600), ), Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, spacing: 16, children: [ SizedBox( height: 40.toScreenHeight, child: ListView.separated( scrollDirection: Axis.horizontal, padding: EdgeInsets.zero, itemBuilder: (cxt, index) => _getActiveFilterChips()[index], separatorBuilder: (cxt, index) => 8.width, itemCount: _getActiveFilterChips().length, ), ).expanded, TextButton( onPressed: () { setState(() { _searchModel = AssetTrackingSearchModel(); getAssetsFromPenguin(); }); }, style: TextButton.styleFrom( padding: EdgeInsets.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), child: Text( 'Clear Filters', style: AppTextStyles.tinyFont.copyWith(color: AppColor.red30), ), ), ], ) ], ), ); } Widget _buildFilterChip({required String label, required VoidCallback onDelete}) { return Theme( data: Theme.of(context).copyWith(splashColor: Colors.transparent, highlightColor: Colors.transparent), child: Chip( backgroundColor: AppColor.backgroundTabBarColor(context), side: BorderSide(color: AppColor.border2Color(context)), visualDensity: VisualDensity.compact, label: Text(label), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30.0), ), labelStyle: AppTextStyles.tinyFont2.copyWith( color: AppColor.labelTextStyleColor(context), ), deleteIconColor: AppColor.icon2Color(context), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, onDeleted: onDelete, ), ); } List _getActiveFilterChips() { final chips = []; // Search if (_searchModel?.search?.isNotEmpty ?? false) { chips.add( _buildFilterChip( label: 'Search: ${_searchModel?.search}', onDelete: () { setState(() { _searchModel?.search = null; getAssetsFromPenguin(searchModel: _searchModel); }); }, ), ); } if (_searchModel?.types?.isNotEmpty ?? false) { for (final type in _searchModel?.types ?? []) { chips.add( _buildFilterChip( label: 'Type: ${type.name}', onDelete: () { setState(() { _searchModel?.types!.remove(type); getAssetsFromPenguin(searchModel: _searchModel); }); }, ), ); } } if (_searchModel?.status?.isNotEmpty ?? false) { for (final status in _searchModel?.status ?? []) { chips.add( _buildFilterChip( label: 'Status: ${status.name}', onDelete: () { setState(() { _searchModel?.status!.remove(status); getAssetsFromPenguin(searchModel: _searchModel); }); }, ), ); } } if (_searchModel?.departments?.isNotEmpty ?? false) { for (final department in _searchModel?.departments ?? []) { chips.add( _buildFilterChip( label: 'Department: ${department.name}', onDelete: () { setState(() { _searchModel?.departments?.remove(department); getAssetsFromPenguin(searchModel: _searchModel); }); }, ), ); } } // Start Date if (_searchModel?.startDate?.isNotEmpty ?? false) { chips.add( _buildFilterChip( label: 'From: ${_searchModel?.startDate?.toAssetDetailsFormat}', onDelete: () { setState(() { _searchModel?.startDate = null; getAssetsFromPenguin(searchModel: _searchModel); }); }, ), ); } // End Date if (_searchModel?.endDate?.isNotEmpty ?? false) { chips.add( _buildFilterChip( label: 'To: ${_searchModel?.endDate?.toAssetDetailsFormat}', onDelete: () { setState(() { _searchModel?.endDate = null; getAssetsFromPenguin(searchModel: _searchModel); }); }, ), ); } return chips; } bool _filterIsNotEmpty() { return (_searchModel?.search?.isNotEmpty ?? false) || (_searchModel?.types != null && _searchModel!.types!.isNotEmpty) || (_searchModel?.status != null && _searchModel!.status!.isNotEmpty) || (_searchModel?.departments != null && _searchModel!.departments!.isNotEmpty) || (_searchModel?.startDate != null && _searchModel!.startDate!.isNotEmpty) || (_searchModel?.endDate != null && _searchModel!.endDate!.isNotEmpty); } }