Merge remote-tracking branch 'origin/main_production_asset_tracking' into main_production_asset_tracking

# Conflicts:
#	lib/modules/asset_tracking/asset_tracking_provider.dart
main_production_asset_tracking
Sikander Saleem 1 week ago
commit 2f2b13c2b0

@ -637,5 +637,9 @@ class URLs {
static get getPenguinDepartments => "$_baseUrl/PenguinIN/departments";
static get getPenguinBuildings => "$_baseUrl/PenguinIN/buildings";
static get getPenguinFloors => "$_baseUrl/PenguinIN/floors";
static get getPenguinStatus => "$_baseUrl/PenguinIN/statuses";
static get getPenguinAssetTypes => "$_baseUrl/PenguinIN/assetTypes";
static get getPenguinTagStatusLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3037";
static get getPenguinTagAssignmentLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3033";
}

@ -137,6 +137,7 @@ import 'controllers/providers/settings/setting_provider.dart';
import 'dashboard_latest/dashboard_provider.dart';
import 'l10n/app_localizations.dart';
import 'modules/asset_tracking/asset_tracking_provider.dart';
import 'modules/asset_tracking/filter_provider.dart';
import 'modules/demo_module/demo_document_lookup_provider.dart';
import 'modules/incident_module/create_incident_request_page.dart';
import 'modules/incident_module/incident_lookup_provider.dart';
@ -405,6 +406,12 @@ class MyApp extends StatelessWidget {
// UTILITY PROVIDERS (1)
ChangeNotifierProvider(create: (_) => OracleCodeProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => AssetTrackingProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => TypeProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => TagAssignmentProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => TagStatusProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => LastSeenProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => AssetTrackingStatusProvider(), lazy: true),
ChangeNotifierProvider(create: (_) => AssetTrackingDepartmentProvider(), lazy: true),
],
child: GestureDetector(
onTap: () {

@ -1,6 +1,7 @@
import 'package:flutter/material.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/new_views/app_style/app_color.dart';
import 'package:test_sa/views/widgets/item_views/info_header_widget.dart';
@ -11,8 +12,9 @@ import 'models/asset_tracking_model.dart';
class AssetTrackingListItemView extends StatelessWidget {
final AssetTrackingModel asset;
bool isGridView;
AssetTrackingListItemView(this.asset, {Key? key}) : super(key: key);
AssetTrackingListItemView(this.asset, this.isGridView, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -36,9 +38,18 @@ class AssetTrackingListItemView extends StatelessWidget {
label: "Last seen",
value: asset.location?.lastSeen?.toServiceRequestDetailsFormat,
showEmptyValue: true,
showArrow: true,
arrowMsg: "View on map",
showArrow: isGridView ? false : true,
arrowMsg: isGridView ? "" : "View on map",
),
if (isGridView) ...[
5.height,
InfoTextWidget(
label: "View on map",
value: '',
showEmptyValue: true,
showArrow: true,
),
],
],
).toShadowContainer(context);
}

@ -1,9 +1,5 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_geojson2/flutter_map_geojson2.dart';
import 'package:latlong2/latlong.dart';
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart';
import 'package:provider/provider.dart';
import 'package:test_sa/extensions/context_extension.dart';
import 'package:test_sa/extensions/int_extensions.dart';
@ -11,13 +7,12 @@ 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/item_views/info_header_widget.dart';
import 'package:test_sa/views/widgets/item_views/info_text_widget.dart';
import 'package:test_sa/views/widgets/loaders/app_loading.dart';
import 'package:test_sa/views/widgets/loaders/no_data_found.dart';
import 'package:test_sa/views/widgets/requests/request_status.dart';
import 'asset_tracking_item_view.dart';
import 'map_tracking_page.dart';
@ -34,6 +29,7 @@ class _AssetTrackingPageState extends State<AssetTrackingPage> with SingleTicker
int selectedIndex = 1;
late AnimationController _animationController;
late AssetTrackingProvider assetTrackingProvider;
AssetTrackingSearchModel? _searchModel;
@override
void initState() {
@ -46,18 +42,55 @@ class _AssetTrackingPageState extends State<AssetTrackingPage> with SingleTicker
_animationController.forward();
WidgetsBinding.instance.addPostFrameCallback((_) {
assetTrackingProvider.reset();
assetTrackingProvider.getAssetsFromPenguin();
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: const DefaultAppBar(title: "Live Asset Tracking"),
appBar: DefaultAppBar(
title: "Live Asset Tracking",
actions: [
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>(
@ -118,48 +151,60 @@ class _AssetTrackingPageState extends State<AssetTrackingPage> with SingleTicker
return Consumer<AssetTrackingProvider>(builder: (context, provider, _) {
return (provider.assetsList.isEmpty && !provider.isLoading)
? NoDataFound(message: context.translation.noDataFound).center
: ListView.separated(
key: const ValueKey('asset-list_view'),
padding: const EdgeInsets.all(16),
itemCount: provider.isLoading ? 6 : provider.assetsList.length,
separatorBuilder: (listContext, itemIndex) => 12.height,
itemBuilder: (listContext, itemIndex) {
if (provider.isLoading) return const SizedBox().toAssetListShimmer(listContext, provider.isLoading);
// Calculate staggered animation delay
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,
: 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,
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])).onPress(() {
Navigator.push(context, MaterialPageRoute(builder: (context) => MapTrackingPage(assetTrackingData: provider.assetsList[itemIndex])));
}),
);
},
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])));
}),
);
},
),
);
});
}
@ -168,62 +213,254 @@ class _AssetTrackingPageState extends State<AssetTrackingPage> with SingleTicker
return Consumer<AssetTrackingProvider>(builder: (context, provider, _) {
return (provider.assetsList.isEmpty && !provider.isLoading)
? NoDataFound(message: context.translation.noDataFound).center
:
// ListView.separated(
//
// padding: const EdgeInsets.all(16),
GridView.builder(
key: const ValueKey('asset-grid_view'),
padding: const EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: .8,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
),
: 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,
),
),
);
itemCount: provider.isLoading ? 6 : provider.assetsList.length,
// separatorBuilder: (listContext, itemIndex) => 12.height,
itemBuilder: (listContext, itemIndex) {
if (provider.isLoading) return const SizedBox().toAssetListShimmer(listContext, provider.isLoading);
// Calculate staggered animation delay
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,
),
),
);
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])));
}),
);
},
),
),
);
return FadeTransition(
opacity: animation,
child: SlideTransition(position: slideAnimation, child: AssetTrackingListItemView(provider.assetsList[itemIndex])).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);
}
}

@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:test_sa/controllers/api_routes/api_manager.dart';
import 'package:test_sa/controllers/api_routes/urls.dart';
import 'package:test_sa/modules/asset_tracking/models/coordinates_model.dart';
import 'package:test_sa/modules/asset_tracking/models/asset_tracking_search_model.dart';
import 'models/asset_tracking_model.dart';
import 'models/floor_map_model.dart';
@ -15,7 +16,7 @@ class AssetTrackingProvider extends ChangeNotifier {
bool isNextPageLoading = false;
final pageItemNumber = 12;
int pageNo = 1;
bool nextPage = false;
List<AssetTrackingModel> assetsList = [];
List<AssetTrackingModel> assetsOnTheFloorList = [];
@ -51,6 +52,9 @@ class AssetTrackingProvider extends ChangeNotifier {
reset() {
isLoading = false;
isNextPageLoading = false;
pageNo = 1;
nextPage = false;
selectedFloor = null;
selectedBuilding = null;
selectedSite = null;
@ -63,45 +67,49 @@ class AssetTrackingProvider extends ChangeNotifier {
notifyListeners();
}
Future<int> getAssetsFromPenguin({bool loadMore = false}) async {
Future<int> getAssetsFromPenguin({bool loadMore = false,AssetTrackingSearchModel? searchModel}) async {
if (isLoading || isNextPageLoading) return -2;
if (loadMore) {
isNextPageLoading = true;
pageNo += 1;
} else {
isLoading = true;
pageNo = 1; // reset pagination
pageNo = 1;
}
notifyListeners();
try {
final payload = {"pageSize": pageItemNumber, "pageNumber": pageNo};
final Map<String, dynamic> payload = {
"pageSize": pageItemNumber,
"pageNumber": pageNo,
};
if (searchModel != null) {
payload.addAll(searchModel.toJson());
}
final response = await ApiManager.instance.post(URLs.getAssetsFromPenguin, body: payload);
if (response.statusCode >= 200 && response.statusCode < 300) {
final Map<String, dynamic> jsonData = json.decode(response.body);
List list = jsonData["data"];
assetsList.addAll(list.map((item) => AssetTrackingModel.fromJson(item)).toList());
if (jsonData["totalRows"] <= assetsList.length) {
// nextPage = true;
List list = jsonData["data"] ?? [];
List<AssetTrackingModel> responseAssetList = list.map((item) => AssetTrackingModel.fromJson(item)).toList();
if (loadMore) {
assetsList.addAll(responseAssetList);
} else {
assetsList = responseAssetList;
}
// if (loadMore) {
// assetInventoryResponse?.assetList?.addAll(newResponse.assetList ?? []);
// assetInventoryResponse?.totalRows = newResponse.totalRows;
// } else {
// assetInventoryResponse = newResponse;
// }
// nextPage = (assetInventoryResponse?.assetList?.length ?? 0) < (assetInventoryResponse?.totalRows ?? 0);
final int totalRows = (jsonData["totalRows"] as int?) ?? assetsList.length;
nextPage = (assetsList.length) < totalRows;
} else {
// nextPage = false;
if (loadMore) pageNo -= 1;
}
isLoading = false;
isNextPageLoading = false;
notifyListeners();
return response.statusCode;
} catch (error) {
if (loadMore) pageNo -= 1;
isLoading = false;
isNextPageLoading = false;
// stateCode = -1;
// nextPage = false;
notifyListeners();
return -1;
}

@ -0,0 +1,253 @@
import 'package:flutter/material.dart';
import 'package:test_sa/extensions/context_extension.dart';
import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/text_extensions.dart';
import 'package:test_sa/extensions/widget_extensions.dart';
import 'package:test_sa/models/lookup.dart';
import 'package:test_sa/modules/asset_tracking/filter_provider.dart';
import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart';
import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/new_views/common_widgets/app_filled_button.dart';
import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart';
import 'package:test_sa/new_views/common_widgets/multiple_item_drop_down_menu.dart';
import 'package:test_sa/views/widgets/date_and_time/date_picker.dart';
import 'package:test_sa/views/widgets/item_views/info_header_widget.dart';
import '../../../new_views/common_widgets/default_app_bar.dart';
import 'models/asset_tracking_search_model.dart';
class AssetTrackingSearchScreen extends StatefulWidget {
static const String id = "asset_tracking_screen";
AssetTrackingSearchModel? searchModel;
AssetTrackingSearchScreen({Key? key, this.searchModel}) : super(key: key);
@override
State<AssetTrackingSearchScreen> createState() => _AssetTrackingSearchScreenState();
}
class _AssetTrackingSearchScreenState extends State<AssetTrackingSearchScreen> {
late AssetTrackingSearchModel searchModel;
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late TextEditingController _searchController;
@override
void initState() {
searchModel = widget.searchModel ?? AssetTrackingSearchModel();
_searchController = TextEditingController(text: searchModel.search);
super.initState();
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: DefaultAppBar(
title: context.translation.search,
showBackButtonAsCancel: true,
actions: [
if (_showResetButton())
TextButton(
onPressed: () {
setState(() {
_searchController.text = "";
searchModel = AssetTrackingSearchModel();
});
},
style: TextButton.styleFrom(padding: EdgeInsets.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap),
child: Text(
'Clear Filters',
style: AppTextStyles.bodyText2.copyWith(color: AppColor.red30),
),
).paddingOnly(start: 16, end: 16),
],
),
body: Form(
key: _formKey,
child: SafeArea(
child: Column(
children: [
ListView(
padding: const EdgeInsets.all(16),
children: [
context.translation.searchBy.heading5(context).custom(color: AppColor.textStyleColor(context)),
4.height,
context.translation.searchByDesc.bodyText2(context).custom(color: AppColor.lightTextColor(context)),
16.height,
AppTextFormField(
controller: _searchController,
labelText: context.translation.search,
onChange: (text) {
_searchController.text = text;
_searchController.selection = TextSelection.fromPosition(TextPosition(offset: _searchController.text.length));
setState(() {});
},
onSaved: (value) {
searchModel.search = value;
},
),
8.height,
MultipleItemDropDownMenu<Lookup, TypeProvider>(
context: context,
title: 'Type',
initialValue: searchModel.types != null ? searchModel.types! : [],
showAsBottomSheet: true,
showShadow: true,
height: 56.toScreenHeight,
showCancel: true,
onSelect: (value) {
if (value != null) {
setState(() {
searchModel.types = value;
});
}
},
),
8.height,
MultipleItemDropDownMenu<Lookup, AssetTrackingDepartmentProvider>(
context: context,
title: 'Department',
initialValue: searchModel.departments != null ? searchModel.departments! : [],
showAsBottomSheet: true,
showShadow: true,
height: 56.toScreenHeight,
showCancel: true,
onSelect: (value) {
if (value != null) {
setState(() {
searchModel.departments = value;
});
}
},
),
8.height,
MultipleItemDropDownMenu<Lookup, AssetTrackingStatusProvider>(
context: context,
title: 'Status',
initialValue: searchModel.status != null ? searchModel.status! : [],
showAsBottomSheet: true,
showShadow: true,
height: 56.toScreenHeight,
showCancel: true,
onSelect: (value) {
if (value != null) {
setState(() {
searchModel.status = value;
});
}
},
),
8.height,
// MultipleItemDropDownMenu<Lookup, LastSeenProvider>(
// context: context,
// title: 'Last Seen',
// initialValue: searchModel.lastSeen != null ? searchModel.lastSeen! : [],
// showAsBottomSheet: true,
// showShadow: true,
// height: 56.toScreenHeight,
// showCancel: true,
// onSelect: (value) {
// if (value != null) {
// setState(() {
// searchModel.lastSeen = value;
// });
// }
// },
// ),
// 8.height,
// MultipleItemDropDownMenu<Lookup, TagStatusProvider>(
// context: context,
// title: 'Tag Status',
// initialValue: searchModel.tagStatus != null ? searchModel.tagStatus! : [],
// showAsBottomSheet: true,
// showShadow: true,
// height: 56.toScreenHeight,
// showCancel: true,
// onSelect: (value) {
// if (value != null) {
// setState(() {
// searchModel.tagStatus = value;
// });
// }
// },
// ),
// 8.height,
// MultipleItemDropDownMenu<Lookup, TagAssignmentProvider>(
// context: context,
// title: 'Tag Assignment',
// initialValue: searchModel.tagAssignment != null ? searchModel.tagAssignment! : [],
// showAsBottomSheet: true,
// showShadow: true,
// height: 56.toScreenHeight,
// showCancel: true,
// onSelect: (value) {
// if (value != null) {
// setState(() {
// searchModel.tagAssignment = value;
// });
// }
// },
// ),
8.height,
InfoHeader16Widget('Last Seen'),
8.height,
Row(
children: [
ADatePicker(
label: context.translation.from,
from: DateTime(2000),
date: DateTime.tryParse(searchModel.startDate ?? ""),
onDatePicker: (date) {
setState(() {
searchModel.startDate = date.toIso8601String();
});
},
).expanded,
8.width,
ADatePicker(
label: context.translation.to,
from: DateTime(2000),
date: DateTime.tryParse(searchModel.endDate ?? ""),
onDatePicker: (date) {
setState(() {
searchModel.endDate = date.toIso8601String();
});
},
).expanded,
],
),
],
).expanded,
FooterActionButton.footerContainer(context: context, child: AppFilledButton(onPressed: _search, label: context.translation.search)),
],
),
),
),
);
}
void _search() {
if (!_formKey.currentState!.validate()) {
return;
}
_formKey.currentState!.save();
Navigator.pop(context, searchModel);
}
bool _showResetButton() {
return (_searchController.text.isNotEmpty) ||
// (searchModel.tagAssignment != null && searchModel.tagAssignment!.isNotEmpty) ||
// (searchModel.tagStatus != null && searchModel.tagStatus!.isNotEmpty) ||
// (searchModel.lastSeen != null && searchModel.lastSeen!.isNotEmpty) ||
(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);
}
}

@ -0,0 +1,182 @@
import 'dart:convert';
import 'package:http/http.dart';
import 'package:test_sa/models/lookup.dart';
import 'package:test_sa/providers/loading_list_notifier.dart';
import '../../controllers/api_routes/api_manager.dart';
import '../../controllers/api_routes/urls.dart';
class AssetTrackingDepartmentProvider extends LoadingListNotifier<Lookup> {
@override
Future getData({int? id}) async {
if (loading) return -2;
loading = true;
notifyListeners();
Response response;
try {
response = await ApiManager.instance.get(URLs.getPenguinDepartments,);
} catch (error) {
loading = false;
stateCode = -1;
notifyListeners();
return -1;
}
stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) {
final List listJson = json.decode(response.body)["data"] ?? [];
items = listJson.map((item) {
return Lookup.fromJson({
...item,
"name": item["label"],
});
}).toList();
}
loading = false;
notifyListeners();
return response.statusCode;
}
}
class AssetTrackingStatusProvider extends LoadingListNotifier<Lookup> {
@override
Future getData({int? id}) async {
if (loading) return -2;
loading = true;
notifyListeners();
Response response;
try {
response = await ApiManager.instance.get(URLs.getPenguinStatus);
} catch (error) {
loading = false;
stateCode = -1;
notifyListeners();
return -1;
}
stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) {
final List listJson = json.decode(response.body)["data"] ?? [];
items = listJson.map((item) {
return Lookup.fromJson({
...item,
"name": item["label"],
});
}).toList();
}
loading = false;
notifyListeners();
return response.statusCode;
}
}
class LastSeenProvider extends LoadingListNotifier<Lookup> {
@override
Future getData({int? id}) async {
if (loading) return -2;
loading = true;
notifyListeners();
Response response;
try {
response = await ApiManager.instance.post(URLs.getDepartments, body: {"pageSize": 50});
} catch (error) {
loading = false;
stateCode = -1;
notifyListeners();
return -1;
}
stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) {
List listJson = json.decode(response.body)["data"];
items = listJson.map((department) => Lookup.fromJson(department)).toList();
}
loading = false;
notifyListeners();
return response.statusCode;
}
}
class TagStatusProvider extends LoadingListNotifier<Lookup> {
@override
Future getData({int? id}) async {
if (loading) return -2;
loading = true;
notifyListeners();
Response response;
try {
response = await ApiManager.instance.get(URLs.getPenguinTagStatusLookup);
} catch (error) {
loading = false;
stateCode = -1;
notifyListeners();
return -1;
}
stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) {
List listJson = json.decode(response.body)["data"];
items = listJson.map((department) => Lookup.fromJson(department)).toList();
}
loading = false;
notifyListeners();
return response.statusCode;
}
}
class TagAssignmentProvider extends LoadingListNotifier<Lookup> {
@override
Future getData({int? id}) async {
if (loading) return -2;
loading = true;
notifyListeners();
Response response;
try {
response = await ApiManager.instance.get(URLs.getPenguinTagAssignmentLookup);
} catch (error) {
loading = false;
stateCode = -1;
notifyListeners();
return -1;
}
stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) {
List listJson = json.decode(response.body)["data"];
items = listJson.map((department) => Lookup.fromJson(department)).toList();
}
loading = false;
notifyListeners();
return response.statusCode;
}
}
class TypeProvider extends LoadingListNotifier<Lookup> {
@override
Future getData({int? id}) async {
if (loading) return -2;
loading = true;
notifyListeners();
Response response;
try {
response = await ApiManager.instance.get(URLs.getPenguinAssetTypes,);
} catch (error) {
loading = false;
stateCode = -1;
notifyListeners();
return -1;
}
stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) {
final List listJson = json.decode(response.body)["data"] ?? [];
items = listJson.map((item) {
return Lookup.fromJson({
...item,
"name": item["label"],
});
}).toList();
}
loading = false;
notifyListeners();
return response.statusCode;
}
}

@ -0,0 +1,64 @@
import 'package:test_sa/models/lookup.dart';
class AssetTrackingSearchModel {
AssetTrackingSearchModel({
this.search,
this.tagStatus,
this.lastSeen,
this.tagAssignment,
this.types,
this.departments,
this.status,
this.startDate,
this.endDate,
});
String? search;
List<Lookup>? tagStatus;
List<Lookup>? lastSeen;
List<Lookup>? tagAssignment;
List<Lookup>? departments;
List<Lookup>? types;
List<Lookup>? status;
String? startDate, endDate;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
if (search != null && search!.trim().isNotEmpty) {
map['searchString'] = search!.trim();
}
if (tagStatus?.isNotEmpty == true) {
map['tagStatusIds'] = tagStatus!.map((e) => e.id).toList();
}
if (lastSeen?.isNotEmpty == true) {
map['lastSeenIds'] = lastSeen!.map((e) => e.id).toList();
}
if (tagAssignment?.isNotEmpty == true) {
map['tagAssignmentIds'] = tagAssignment!.map((e) => e.id).toList();
}
if (types?.isNotEmpty == true) {
map['typeIds'] = types!.map((e) => e.id).toList();
}
if (departments?.isNotEmpty == true) {
map['departmentIds'] = departments!.map((e) => e.id).toList();
}
if (status?.isNotEmpty == true) {
map['statusIds'] = status!.map((e) => e.id).toList();
}
if (startDate != null && startDate!.trim().isNotEmpty) {
map['lastSeenFrom'] = startDate!.trim();
}
if (endDate != null && endDate!.trim().isNotEmpty) {
map['lastSeenTo'] = endDate!.trim();
}
return map;
}
}
Loading…
Cancel
Save