You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cloudsolutions-atoms/lib/modules/asset_tracking/asset_tracking_page.dart

467 lines
18 KiB
Dart

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/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<AssetTrackingPage> with SingleTickerProviderStateMixin {
int selectedIndex = 1;
late AnimationController _animationController;
late AssetTrackingProvider assetTrackingProvider;
AssetTrackingSearchModel? _searchModel;
@override
void initState() {
super.initState();
assetTrackingProvider = Provider.of<AssetTrackingProvider>(context, listen: false);
_animationController = AnimationController(
duration: const Duration(milliseconds: 1200),
vsync: this,
);
_animationController.forward();
WidgetsBinding.instance.addPostFrameCallback((_) {
assetTrackingProvider.reset();
getAssetsFromPenguin();
});
}
Future<void> 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<AssetTrackingSearchModel>(
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<int>(
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<int>>[
ButtonSegment<int>(
value: 1,
label: const Text("List"),
icon: Icon(Icons.list_rounded, color: AppColor.icon2Color(context)),
),
ButtonSegment<int>(
value: 2,
label: const Text("Grid"),
icon: Icon(Icons.grid_view_rounded, color: AppColor.icon2Color(context)),
),
ButtonSegment<int>(
value: 3,
label: const Text("Map"),
icon: Icon(Icons.map, color: AppColor.icon2Color(context)),
),
],
selected: <int>{selectedIndex},
onSelectionChanged: (Set<int> newSelection) {
setState(() {
selectedIndex = newSelection.first;
});
},
),
),
SafeArea(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: selectedIndex == 1
? assetsListView()
: selectedIndex == 2
? assetsGridView()
: MapTrackingPage()
// selectedIndex == 3 ? MapTrackingPage() : assetsListView(),
),
).expanded,
],
),
);
}
Widget assetsListView() {
return Consumer<AssetTrackingProvider>(builder: (context, provider, _) {
return (provider.assetsList.isEmpty && !provider.isLoading)
? NoDataFound(message: context.translation.noDataFound).center
: NotificationListener<ScrollNotification>(
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<double>(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<Offset>(
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<AssetTrackingProvider>(builder: (context, provider, _) {
return (provider.assetsList.isEmpty && !provider.isLoading)
? NoDataFound(message: context.translation.noDataFound).center
: NotificationListener<ScrollNotification>(
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: 1.1, // 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<double>(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<Offset>(
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<Widget> _getActiveFilterChips() {
final chips = <Widget>[];
// 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);
}
}