Charts Added

This commit is contained in:
venbaittech 2025-01-30 16:58:50 +05:30
parent 1a39a7c740
commit 5c5243d02e
10 changed files with 1737 additions and 542 deletions

View File

@ -374,6 +374,7 @@ final GoRouter router = GoRouter(
'0xFFFFFFFF'; // Default white
final mainTopic = state.uri.queryParameters['mainTopic'] ?? '';
final title = state.uri.queryParameters['title'] ?? '';
final key = state.uri.queryParameters['key'] ?? '';
print('Router dataSets: $dataSets');
print('Router bgColor: $bgColor');
@ -386,6 +387,7 @@ final GoRouter router = GoRouter(
bgColor: bgColor,
mainTopic: mainTopic,
title: title,
keyParam: key,
);
},
),

View File

@ -1,7 +1,9 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:uae_stat/presentation/components/indicators/locale_provider.dart';
import 'package:uae_stat/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart';
import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart';
import 'package:uae_stat/presentation/components/constant/constant.dart';
@ -23,28 +25,31 @@ class UaeNumbers extends StatelessWidget {
}
}
class uaenumberWidget extends StatefulWidget {
class uaenumberWidget extends ConsumerStatefulWidget {
const uaenumberWidget({super.key});
@override
_UaenumberWidgetState createState() => _UaenumberWidgetState();
ConsumerState<uaenumberWidget> createState() => _UaenumberWidgetState();
}
class _UaenumberWidgetState extends State<uaenumberWidget> {
class _UaenumberWidgetState extends ConsumerState<uaenumberWidget> {
@override
List<dynamic> homePageData = [];
bool isLoading = true;
int? expandedIndex = 0;
@override
void initState() {
fetchData();
super.initState();
final locale = ref.read(localeProvider);
fetchData(locale?.languageCode ?? 'en');
}
Future<void> fetchData() async {
Future<void> fetchData(locale) async {
// const baseUrl = 'https://pb.venbait.in/api/getHomePageData';
const baseUrl = 'https://pb.venbait.in/api/getUAENumbersData';
try {
final response = await http.get(Uri.parse(baseUrl));
final response = await http.get(Uri.parse(baseUrl + '?language=$locale'));
if (response.statusCode == 200) {
setState(() {
homePageData = json.decode(response.body);
@ -63,6 +68,10 @@ class _UaenumberWidgetState extends State<uaenumberWidget> {
}
Widget build(BuildContext context) {
ref.listen<Locale?>(localeProvider, (previous, next) {
final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null
fetchData(localeCode);
});
double myheight = MediaQuery.of(context).size.height;
double mywidth = MediaQuery.of(context).size.width;
@ -99,7 +108,12 @@ class _UaenumberWidgetState extends State<uaenumberWidget> {
return CustomExpandableTile(
index: index,
isExpanded: isFirstTile,
isExpanded: expandedIndex == index, // Compare with expandedIndex
onTap: (index) {
setState(() {
expandedIndex = (expandedIndex == index) ? null : index;
});
},
title: mainTopic['main_topic'],
titleBackgroundColor: backgroundColor,
children: _buildSubTopics(mainTopic['sub_topics'] ?? [], mywidth,
@ -280,6 +294,8 @@ class CustomExpandableTile extends StatefulWidget {
final List<Widget> children;
final int index;
final bool isExpanded;
final ValueChanged<int> onTap;
const CustomExpandableTile({
required this.title,
@ -287,6 +303,8 @@ class CustomExpandableTile extends StatefulWidget {
required this.children,
required this.index,
required this.isExpanded,
required this.onTap,
});
@override
@ -294,14 +312,12 @@ class CustomExpandableTile extends StatefulWidget {
}
class _CustomExpandableTileState extends State<CustomExpandableTile> {
// bool isExpanded = false;
late bool isExpanded;
bool isExpanded = false;
// late bool isExpanded;
@override
void initState() {
super.initState();
isExpanded =
widget.isExpanded; // Initialize isExpanded based on widget's property
super.initState(); // Initialize isExpanded based on widget's property
}
@override
@ -312,11 +328,8 @@ class _CustomExpandableTileState extends State<CustomExpandableTile> {
child: Column(
children: [
GestureDetector(
onTap: () {
setState(() {
isExpanded = !isExpanded;
});
},
onTap: () => widget.onTap(widget.index),
child: Container(
decoration: BoxDecoration(
color: widget.titleBackgroundColor,
@ -335,7 +348,7 @@ class _CustomExpandableTileState extends State<CustomExpandableTile> {
),
),
Icon(
isExpanded
widget.isExpanded
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down,
color: Colors.white,
@ -350,8 +363,8 @@ class _CustomExpandableTileState extends State<CustomExpandableTile> {
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: double.infinity,
height: isExpanded ? myheight * 0.52 : 0,
child: isExpanded
height: widget.isExpanded ? myheight * 0.52 : 0,
child: widget.isExpanded
? SingleChildScrollView(
child: Container(
decoration: BoxDecoration(

View File

@ -205,7 +205,31 @@ class _CreateNewPwState extends State<CreateNewPw> {
padding: const EdgeInsets.all(24.0),
child: Column(
children: [
// SizedBox(height: screenHeight / 7),
Align(
alignment: Alignment.topRight,
child: GestureDetector(
onTap: () {
context.go('/editProfile');
},
child: Container(
margin: EdgeInsets.only(top: 16,left: 16,bottom: 16,right: 1), // Add margin for positioning
width: 30, // Circle diameter
height: 30,
decoration: BoxDecoration(
color: Colors.grey[300], // Circle color
shape: BoxShape.circle,
),
child: Icon(
Icons.close,
size: 20, // Icon size
color: Colors.white, // Icon color
),
),
),
),
SizedBox(height: screenHeight / 6),
Text(
// "Create New Password",
AppLocalizations.of(context)!.create_new_password,

View File

@ -1,15 +1,20 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:uae_stat/domain/use_cases/language.dart';
import 'package:uae_stat/presentation/Screens/charts/services/api_service.dart';
import 'package:uae_stat/presentation/Screens/charts/widgets/chart_widget.dart';
import 'package:uae_stat/presentation/components/indicators/locale_provider.dart';
import '../filters/search_filter_helper.dart';
class ChartScreen1 extends StatefulWidget {
class ChartScreen1 extends ConsumerStatefulWidget {
final String dataSets;
final String bgColor;
final String mainTopic;
final String title;
final String? keyParam; // Nullable String
const ChartScreen1({
Key? key,
@ -17,18 +22,20 @@ class ChartScreen1 extends StatefulWidget {
required String this.bgColor,
required this.mainTopic,
required this.title,
required this.keyParam,
});
@override
_ChartScreen1State createState() => _ChartScreen1State();
ConsumerState<ChartScreen1> createState() => _ChartScreen1State();
}
class _ChartScreen1State extends State<ChartScreen1> {
class _ChartScreen1State extends ConsumerState<ChartScreen1> {
List<Map<String, dynamic>> selectedFiltersStorage = [];
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final ApiService apiService = ApiService();
bool isLoading = true;
List<dynamic> isChartData = [];
List<dynamic> nonChartData = [];
List<dynamic> filterDataSet = [];
List<dynamic> filterData = [];
List<dynamic> chartsData = [];
List<dynamic> tabFilteredChartData = [];
@ -50,7 +57,8 @@ class _ChartScreen1State extends State<ChartScreen1> {
final String bgColor = widget.bgColor;
print(' bgColor $bgColor');
// fetchChartData(widget.dataSets);
fetchChartData(widget.dataSets).then((_) {
final locale = ref.read(localeProvider);
fetchChartData(widget.dataSets, locale?.languageCode ?? 'en').then((_) {
if (_tabsData.isNotEmpty) {
// Call onTabSelected for the first tab
onTabSelected(_tabsData[0]['id']);
@ -74,6 +82,7 @@ class _ChartScreen1State extends State<ChartScreen1> {
});
_scrollToIndex(_activeTabIndex);
onTabSelected(_tabsData[_activeTabIndex]['id']!); // Pass the tab's id
print("TABFiltered Data: $filterData");
}
}
@ -87,9 +96,39 @@ class _ChartScreen1State extends State<ChartScreen1> {
}
}
// void processChartData(chartsData) {
// // Group data by 'kpi'
// Map<String, List<Map<String, dynamic>>> groupedData = {};
// for (var chart in chartsData) {
// String kpi = chart['kpi'] ?? '';
// if (!groupedData.containsKey(kpi)) {
// groupedData[kpi] = [];
// }
// groupedData[kpi]!.add(chart);
// }
//
// // Format 'kpi' values for _tabs
// List<Map<String, String>> _tabs = groupedData.keys.map((kpi) {
// String name = kpi
// .split('_') // Split by underscore
// .map(
// (word) => word[0].toUpperCase() + word.substring(1)) // Capitalize
// .join(' '); // Join words with space
//
// return {'id': kpi, 'name': name};
// }).toList();
//
// print('Grouped Data: $groupedData');
// print('Tabs: $_tabs');
// setState(() {
// _tabsData = _tabs;
// });
// }
void processChartData(chartsData) {
// Group data by 'kpi'
Map<String, List<Map<String, dynamic>>> groupedData = {};
for (var chart in chartsData) {
String kpi = chart['kpi'] ?? '';
if (!groupedData.containsKey(kpi)) {
@ -98,30 +137,52 @@ class _ChartScreen1State extends State<ChartScreen1> {
groupedData[kpi]!.add(chart);
}
// Format 'kpi' values for _tabs
List<Map<String, String>> _tabs = groupedData.keys.map((kpi) {
String name = kpi
// Format 'kpi' values for _tabs with tab_heading
List<Map<String, String>> _tabs = groupedData.entries.map((entry) {
String kpi = entry.key;
// Extract tab_heading from the first chart in the grouped list
String tabHeading = entry.value.isNotEmpty
? entry.value.first['tab_heading'] ?? 'Unknown'
: 'Unknown';
String formattedKpi = kpi
.split('_') // Split by underscore
.map(
(word) => word[0].toUpperCase() + word.substring(1)) // Capitalize
.map((word) => word.isNotEmpty
? word[0].toUpperCase() + word.substring(1)
: '') // Capitalize
.join(' '); // Join words with space
return {'id': kpi, 'name': name};
return {'id': kpi, 'name': tabHeading};
}).toList();
print('Grouped Data: $groupedData');
print('Tabs: $_tabs');
setState(() {
_tabsData = _tabs;
});
}
Future<void> fetchChartData(String dataSets) async {
var data = await apiService.fetchChartData(dataSets);
Future<void> fetchChartData(String dataSets, locale) async {
var data = await apiService.fetchChartData(dataSets, locale);
if (data.containsKey('filterData')) {
var filterData = data['filterData'];
filterDataSet = filterData.entries
.map((entry) => {'key': entry.key, 'value': entry.value})
.toList();
print("filterDataf1:- $filterData");
print("filterDataf11:- $filterDataSet");
} else {
filterDataSet = data['filterData'] ?? [];
print('filterData not found!');
}
setState(() {
isChartData = data['isChartData'] ?? [];
nonChartData = data['nonChartData'] ?? [];
filterData = data['filterData'] ?? [];
// filterData = data['filterData'] ?? [];
originalChartsData = List.from(isChartData); // Store original data
originalCardData = List.from(nonChartData);
@ -133,17 +194,19 @@ class _ChartScreen1State extends State<ChartScreen1> {
print('chartsData :- $chartsData');
print('cardData :- $cardData');
print('filterData :- $filterData');
// print('filterData :- $filterData');
isLoading = false;
});
print(chartsData);
print(cardData);
print('chartsData -$chartsData');
print('cardData -$cardData');
}
void applyFilters(BuildContext context, List filters, List data,
List dataCard, List selectedFilters) {
print("Selected Filters before applying: $selectedFilters");
// Check if all filter_data is empty
print("applyFilters called");
print("Selected ApplyFilters: $selectedFilters");
print("Selected data: $data");
if (selectedFilters.every((filter) => filter['filter_data'].isEmpty)) {
setState(() {
chartsData =
@ -157,8 +220,9 @@ class _ChartScreen1State extends State<ChartScreen1> {
// Loop through each chart data in the `data` list
List filteredData = [];
Set<String> addedChartIds = {}; // Track unique chart identifiers
for (var chart in data) {
// Extract response data for filtering
final groupBy = chart['group_by'];
List response = chart['response'] ?? [];
// Filter the response based on selected filters
@ -171,20 +235,31 @@ class _ChartScreen1State extends State<ChartScreen1> {
final filterValues = filter['filter_data'];
// Ensure the filter_key exists in the `ObsKey` and check if it matches any of the `filter_data`
if (obsKey.containsKey(filterKey)) {
if (groupBy == filterKey && obsKey.containsKey(filterKey)) {
final obsKeyValue = obsKey[filterKey]?.toString();
return filterValues.isEmpty || filterValues.contains(obsKeyValue);
}
return false;
if (filterKey == 'TIME_PERIOD' && obsKey.containsKey('TIME_PERIOD')) {
final timePeriodValue = obsKey['TIME_PERIOD']?.toString();
return filterValues.isEmpty ||
filterValues.contains(timePeriodValue);
}
return true;
});
}).toList();
// If any data matches the filter, add the whole chart data object
if (chartFilteredData.isNotEmpty) {
// Add filtered chart only once
if (chartFilteredData.isNotEmpty &&
!addedChartIds.contains(chart['chart_heading'])) {
filteredData.add({
...chart, // Include all other properties of the chart object
'response': chartFilteredData, // Only include filtered response data
...chart,
'response': chartFilteredData,
});
addedChartIds
.add(chart['chart_heading']); // Track by a unique identifier
}
}
@ -224,7 +299,7 @@ class _ChartScreen1State extends State<ChartScreen1> {
// Update the chartsData with the filtered data
setState(() {
chartsData = filteredData;
cardData = filteredCardData;
cardData = filteredCardData; // Adjust this part as needed
});
print("Filtered Data: $filteredData");
@ -271,7 +346,10 @@ class _ChartScreen1State extends State<ChartScreen1> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Filters',
context.translate(
'Filters',
'المرشحات',
),
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
@ -357,7 +435,12 @@ class _ChartScreen1State extends State<ChartScreen1> {
setState(() {});
Navigator.pop(context);
},
child: Text('OK'),
child: Text(
context.translate(
'OK',
'نعم',
),
),
),
],
);
@ -428,7 +511,10 @@ class _ChartScreen1State extends State<ChartScreen1> {
selectedFiltersStorage.clear();
Navigator.pop(context);
},
child: Text('Clear'),
child: Text(context.translate(
'Clear',
'واضح',
)),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey,
),
@ -447,7 +533,10 @@ class _ChartScreen1State extends State<ChartScreen1> {
// Save the selected filters to storage after applying
selectedFiltersStorage = List.from(selectedFilters);
},
child: Text('Apply Filter'),
child: Text(context.translate(
'Apply Filter',
'تطبيق الفلتر',
)),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
),
@ -466,6 +555,28 @@ class _ChartScreen1State extends State<ChartScreen1> {
void onTabSelected(String tabId) {
print('Selected Tab: $tabId');
selectedFiltersStorage.clear();
// Filter the filterDataSet
filterData = filterDataSet
.where((item) => item['key'] == tabId)
.map((item) => item['value'])
.where((item) => item != null && item is Iterable)
.expand((item) => item)
.toList();
for (var item in filterData) {
if (item['filter_key'] == 'TIME_PERIOD') {
// Convert the values to integers, sort them, and convert back to strings
List<int> timePeriodData = item['filter_data']
.map<int>((e) => int.parse(e.toString())) // Convert to int
.toList();
timePeriodData.sort((a, b) => a.compareTo(b)); // Sort numerically
// Optionally, convert sorted integers back to strings if necessary
item['filter_data'] = timePeriodData.map((e) => e.toString()).toList();
}
}
print("TABFiltered Data: $filterData");
setState(() {
chartsData = originalChartsData;
cardData = originalCardData;
@ -507,22 +618,38 @@ class _ChartScreen1State extends State<ChartScreen1> {
// For example, you can update the chart data or display the results
}
double calculateAspectRatio(int itemCount) {
// Modify the logic based on your layout requirements
if (itemCount <= 2) {
return 190.5 / 180;
} else if (itemCount == 3) {
return 180.0 / 300;
} else if (itemCount == 4) {
return 190.5 / 180;
// double calculateAspectRatio(int itemCount) {
// // Modify the logic based on your layout requirements
// if (itemCount <= 2) {
// return 190.5 / 180;
// } else if (itemCount == 3) {
// return 180.0 / 250;
// } else if (itemCount == 4) {
// return 190.5 / 180;
// } else {
// return 180.0 / 180; // Default for more items
// // return 190.5 / 180;
// }
// }
double calculateAspectRatio(int crossAxisCount, List<dynamic> cardData) {
// Determine a default aspect ratio based on the most common chart type in the list
if (cardData.any((item) =>
item['chart_type'] == 'total' || item['chart_type'] == 'average')) {
return crossAxisCount == 2 ? 1.0 : 0.7; // Larger Content
// return crossAxisCount == 2 ? 1.5 : 0.9;
} else {
return 180.0 / 260; // Default for more items
// return 190.5 / 180;
return crossAxisCount == 2 ? 1.5 : 0.9;
// Shorter Content
}
}
@override
Widget build(BuildContext context) {
ref.listen<Locale?>(localeProvider, (previous, next) {
final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null
fetchChartData(widget.dataSets, localeCode);
});
double myheight = MediaQuery.of(context).size.height;
double mywidth = MediaQuery.of(context).size.width;
final color =
@ -536,11 +663,18 @@ class _ChartScreen1State extends State<ChartScreen1> {
leading: IconButton(
icon: Icon(Icons.arrow_back_ios_new, color: Colors.white),
onPressed: () {
context.go('/uaenumbers');
if (widget.keyParam == 'home') {
context.go('/myhomepage');
} else {
context.go('/uaenumbers');
}
},
),
title: Text(
'UAE Numbers',
context.translate(
'UAE Numbers',
'أرقام الإمارات',
),
style: TextStyle(color: Colors.white),
),
),
@ -609,7 +743,10 @@ class _ChartScreen1State extends State<ChartScreen1> {
Row(
children: [
Text(
'Bookmark',
context.translate(
'Bookmark',
'إشارة مرجعية',
),
style: const TextStyle(
fontSize: 16,
color: Colors.white,
@ -624,7 +761,10 @@ class _ChartScreen1State extends State<ChartScreen1> {
Row(
children: [
Text(
'Share',
context.translate(
'Share',
'يشارك',
),
style: const TextStyle(
fontSize: 16,
color: Colors.white,
@ -653,33 +793,37 @@ class _ChartScreen1State extends State<ChartScreen1> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Left arrow button
_buildArrowButton(
onPressed: _activeTabIndex > 0 ? _scrollLeft : null,
icon: Icons.arrow_back_ios_new,
),
if (_tabsData.length > 1)
_buildArrowButton(
onPressed:
_activeTabIndex > 0 ? _scrollLeft : null,
icon: Icons.arrow_back_ios_new,
),
// Tabs with horizontal scroll
Expanded(
child: SingleChildScrollView(
controller: _scrollController,
scrollDirection: Axis.horizontal,
child: Row(
children:
List.generate(_tabsData.length, (index) {
return _buildTab(
_tabsData[index],
isActive: index == _activeTabIndex,
);
}),
if (_tabsData.length > 1)
Expanded(
child: SingleChildScrollView(
controller: _scrollController,
scrollDirection: Axis.horizontal,
child: Row(
children:
List.generate(_tabsData.length, (index) {
return _buildTab(
_tabsData[index],
isActive: index == _activeTabIndex,
);
}),
),
),
),
),
// Right arrow button
_buildArrowButton(
onPressed: _activeTabIndex < _tabsData.length - 1
? _scrollRight
: null,
icon: Icons.arrow_forward_ios,
),
if (_tabsData.length > 1)
_buildArrowButton(
onPressed: _activeTabIndex < _tabsData.length - 1
? _scrollRight
: null,
icon: Icons.arrow_forward_ios,
),
],
),
// Expanded(
@ -699,25 +843,32 @@ class _ChartScreen1State extends State<ChartScreen1> {
physics: const NeverScrollableScrollPhysics(),
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: cardData.length == 2
? 2
: cardData.length == 3
? 3
: cardData.length == 4
? 2
: 3, // Default to 3 if more than 4 items // 2 cards per row
crossAxisSpacing: 2,
mainAxisSpacing: 5,
childAspectRatio:
calculateAspectRatio(cardData.length)),
crossAxisCount: cardData.length == 2
? 2
: cardData.length == 3
? 3
: cardData.length == 4
? 2
: 3, // Default to 3 if more than 4 items // 2 cards per row
crossAxisSpacing: 2,
mainAxisSpacing: 5,
childAspectRatio:
calculateAspectRatio(cardData.length, cardData),
),
itemBuilder: (context, index) {
final item = cardData[index];
print('item item item $item');
final chart_type = item['chart_type'];
final chart_heading = item['chart_heading'];
final card_logo = item['card_logo'];
final data = apiService.processNonChartData(item);
print('processNonChartData');
double cardHeight = (chart_type == 'totals' ||
chart_type == 'averages')
? 180.0
: 130.0;
if (chart_type == 'total') {
return Card(
margin: const EdgeInsets.all(10),
@ -725,21 +876,41 @@ class _ChartScreen1State extends State<ChartScreen1> {
borderRadius: BorderRadius.circular(12),
),
elevation: 4,
child: Padding(
child: Container(
height: cardHeight,
padding: const EdgeInsets.all(10.0),
child: Column(
mainAxisSize: MainAxisSize
.min, // Adjust card height based on content
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.public,
color: Color(0xFF90B0D5), size: 30),
// const Icon(Icons.public,
// color: Color(0xFF90B0D5), size: 30),
Image.network(
card_logo ?? '',
width: 30,
height: 30,
errorBuilder:
(context, error, stackTrace) {
return Icon(Icons.public,
color: Color(0xFF90B0D5),
size: 30); // Fallback icon
},
),
const SizedBox(height: 3),
Text(
'${chart_heading ?? 'NA'}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: Colors.black87,
Flexible(
fit: FlexFit.loose,
child: FittedBox(
// fit: BoxFit.contain,
child: Text(
'${chart_heading ?? 'NA'}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: Colors.black87,
),
),
),
),
Text(
@ -747,13 +918,19 @@ class _ChartScreen1State extends State<ChartScreen1> {
style: const TextStyle(
fontSize: 11, color: Colors.grey),
),
Text(
apiService.formatAmount(
data['lastYearValue']),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w900,
color: Color(0xFF90B0D5),
Flexible(
fit: FlexFit.loose,
child: FittedBox(
fit: BoxFit.contain,
child: Text(
apiService.formatAmount(
data['lastYearValue']),
style: const TextStyle(
fontSize: 26,
fontWeight: FontWeight.w900,
color: Color(0xFF90B0D5),
),
),
),
),
SizedBox(
@ -764,19 +941,27 @@ class _ChartScreen1State extends State<ChartScreen1> {
1, // Divider line thickness
),
),
Text(
apiService.formatAmount(
data['secondLastYearValue']),
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFFD83731),
Flexible(
fit: FlexFit.loose,
child: FittedBox(
fit: BoxFit.contain,
child: Text(
apiService.formatAmount(
data['secondLastYearValue']),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFFD83731),
),
),
),
),
Text(
'(${data['secondLastYear'] ?? 'NA'})',
style: const TextStyle(
fontSize: 11, color: Colors.grey),
fontSize: 8,
fontWeight: FontWeight.w500,
color: Colors.grey),
),
],
),
@ -789,13 +974,24 @@ class _ChartScreen1State extends State<ChartScreen1> {
borderRadius: BorderRadius.circular(12),
),
elevation: 4,
child: Padding(
child: Container(
height: cardHeight,
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.analytics,
color: Color(0xFF90B0D5), size: 30),
Image.network(
card_logo ?? '',
width: 30,
height: 30,
errorBuilder:
(context, error, stackTrace) {
return Icon(Icons.public,
color: Color(0xFF90B0D5),
size: 30); // Fallback icon
},
),
const SizedBox(height: 5),
Text(
'${chart_heading ?? 'NA'}',
@ -811,12 +1007,133 @@ class _ChartScreen1State extends State<ChartScreen1> {
style: const TextStyle(
fontSize: 11, color: Colors.grey),
),
Flexible(
fit: FlexFit.loose,
child: FittedBox(
fit: BoxFit.contain,
child: Text(
'${data['roundedAverage'] ?? 'NA'}',
style: const TextStyle(
fontSize: 26,
fontWeight: FontWeight.w900,
color: Color(0xFF90B0D5),
),
),
),
),
],
),
),
);
} else if (chart_type == 'totals') {
return Card(
margin: const EdgeInsets.all(10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 4,
child: Container(
height: cardHeight,
// height: maxHeight,
// padding: const EdgeInsets.all(10.0),
padding: const EdgeInsets.symmetric(
vertical: 0, horizontal: 10),
child: Column(
// mainAxisSize: MainAxisSize.min, // Adjust card height based on content
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.public,
color: Color(0xFF90B0D5), size: 30),
const SizedBox(height: 3),
Flexible(
fit: FlexFit.loose,
child: FittedBox(
// fit: BoxFit.contain,
child: Text(
'${chart_heading ?? 'NA'}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: Colors.black87,
),
),
),
),
Text(
'${data['roundedAverage'] ?? 'NA'}',
'(${data['lastYear'] ?? 'NA'})',
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.w900,
color: Color(0xFF90B0D5),
fontSize: 11, color: Colors.grey),
),
Flexible(
fit: FlexFit.loose,
child: FittedBox(
fit: BoxFit.contain,
child: Text(
apiService.formatAmount(
data['lastYearValue']),
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.w900,
color: Color(0xFF90B0D5),
),
),
),
),
],
),
),
);
} else if (chart_type == 'averages') {
return Card(
margin: const EdgeInsets.all(10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 4,
child: Container(
// height: maxHeight,
// height: 200,
height: cardHeight,
padding: const EdgeInsets.all(5.0),
// padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
child: Column(
// mainAxisSize: MainAxisSize.min, // Adjust card height based on content
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.public,
color: Color(0xFF90B0D5), size: 30),
const SizedBox(height: 5),
Text(
'${chart_heading ?? 'NA'}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: Colors.black87,
),
),
Text(
'(${data['lastYear'] ?? 'NA'})',
style: const TextStyle(
fontSize: 11, color: Colors.grey),
),
const SizedBox(height: 5),
Flexible(
fit: FlexFit.loose,
child: FittedBox(
fit: BoxFit.contain,
child: Text(
apiService.formatAmount(
data['lastYearValue']),
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.w900,
color: Color(0xFF90B0D5),
),
),
),
),
],

View File

@ -13,13 +13,13 @@ class ChartData {
class ApiService {
static const String baseUrl = 'https://pb.venbait.in/api/getDataSet';
Future<Map<String, dynamic>> fetchChartData(String dataSets) async {
Future<Map<String, dynamic>> fetchChartData(String dataSets, locale) async {
List<dynamic> isChartData = [];
List<dynamic> nonChartData = [];
List<dynamic> originalChartsData = [];
List<dynamic> originalCardData = [];
final url = Uri.parse('$baseUrl?dataset=$dataSets');
final url = Uri.parse('$baseUrl?dataset=$dataSets&language=$locale');
try {
final response = await http.get(url);
@ -43,7 +43,8 @@ class ApiService {
return {
'isChartData': isChartData,
'nonChartData': nonChartData,
'filterData': jsonData['filter_data']
'filterData': jsonData['new_filter_data']
// 'filterData': jsonData['filter_data']
// 'originalChartsData': originalChartsData,
// 'originalCardData': originalCardData,
};

File diff suppressed because it is too large Load Diff

View File

@ -461,8 +461,16 @@ class LoginRoute extends HookConsumerWidget {
final continueAsGuestBtn = SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () =>
context.go('/myhomepage'),
onPressed: () async {
final prefs = await SharedPreferences.getInstance();
prefs.clear();
final userId = 'guest';
if (userId.isNotEmpty) {
await saveUserId(userId);
}
context.go('/myhomepage');
//context.go('/${context.language}/${BottomNavBarItem.home.routePath}'),
},
//context.go('/${context.language}/${BottomNavBarItem.home.routePath}'),
style: ButtonStyle(
shape: WidgetStatePropertyAll(

View File

@ -163,43 +163,21 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:uae_stat/presentation/components/indicators/locale_provider.dart';
import '../../drawer_routes/custom_drawer_routes.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:http/http.dart' as http;
class MyHomePage extends StatefulWidget {
class MyHomePage extends ConsumerStatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
ConsumerState<MyHomePage> createState() => _MyHomePageState();
}
void handleInfoCardClick(BuildContext context, String data, Color color) {
// Handle navigation and pass dynamic data
print(data);
final dataSets = data;
if (dataSets != null) {
// Perform navigation
context.go('/chartScreen/$dataSets');
} else {
// Show error if dataSet is null
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('No dataset available')),
);
}
// final dataSets = data;
// print(dataSets);
// if (dataSets == 'hotels') {
// context.go('/chartScreen/$dataSets');
// } else if (dataSets == 'divorces') {
// context.go('/chartScreen/$dataSets');
// } else if (dataSets == 'marriages') {
// context.go('/Chart/$dataSets');
// }
}
class _MyHomePageState extends State<MyHomePage> {
class _MyHomePageState extends ConsumerState<MyHomePage> {
@override
Widget build(BuildContext context) {
double myheight = MediaQuery.of(context).size.height;
@ -215,14 +193,14 @@ class _MyHomePageState extends State<MyHomePage> {
}
}
class EconomyStatsWidget extends StatefulWidget {
class EconomyStatsWidget extends ConsumerStatefulWidget {
const EconomyStatsWidget({super.key});
@override
EconomyStatsState createState() => EconomyStatsState();
ConsumerState createState() => EconomyStatsState();
}
class EconomyStatsState extends State<EconomyStatsWidget> {
class EconomyStatsState extends ConsumerState<EconomyStatsWidget> {
List<dynamic> data = [];
bool isLoading = true;
@ -230,13 +208,22 @@ class EconomyStatsState extends State<EconomyStatsWidget> {
@override
void initState() {
super.initState();
fetchData();
final locale = ref.read(localeProvider);
fetchData(locale?.languageCode ?? 'en');
}
Future<void> fetchData() async {
// @override
// void didChangeDependencies() {
// super.didChangeDependencies();
// // Access the provider here
// final locale = ref.watch(localeProvider);
// fetchData(locale); // Pass the locale to the fetchData method
// }
Future<void> fetchData(locale) async {
const baseUrl = 'https://pb.venbait.in/api/getHomePageData';
try {
final response = await http.get(Uri.parse(baseUrl));
final response = await http.get(Uri.parse(baseUrl + '?language=$locale'));
if (response.statusCode == 200) {
setState(() {
data = json.decode(response.body);
@ -283,6 +270,10 @@ class EconomyStatsState extends State<EconomyStatsWidget> {
}
Widget build(BuildContext context) {
ref.listen<Locale?>(localeProvider, (previous, next) {
final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null
fetchData(localeCode);
});
double myheight = MediaQuery.of(context).size.height;
double mywidth = MediaQuery.of(context).size.width;
@ -386,7 +377,8 @@ class EconomyStatsState extends State<EconomyStatsWidget> {
children: _buildRows(
[tileData],
borderColor,
mainTopic['color_pattern']),
mainTopic['color_pattern'],
mainTopic['main_topic']),
),
),
],
@ -452,8 +444,8 @@ class EconomyStatsState extends State<EconomyStatsWidget> {
// return rows;
// }
List<Widget> _buildRows(
List<Map<String, dynamic>> tileData, Color borderColor, color_pattern) {
List<Widget> _buildRows(List<Map<String, dynamic>> tileData, Color borderColor,
String colorPattern, String mainTopic) {
// tileData.sort((a, b) =>
// (a['data_set_list_order'] ?? 0).compareTo(b['data_set_list_order'] ?? 0),);
@ -479,8 +471,10 @@ List<Widget> _buildRows(
dataset: tile['data_set']!,
bordercolor: borderColor,
textcolor: borderColor,
colorPattern: colorPattern,
backgroundColor:
Colors.white, // Set background color for InfoCard
mainTopic: mainTopic,
onTap: () {},
),
);
@ -529,10 +523,12 @@ class RoundedCornerContainer extends StatelessWidget {
class InfoCard extends StatelessWidget {
final String title;
final String mainTopic;
final String subtitle;
final String value;
final String dataset;
final Color bordercolor;
final String colorPattern;
final Color? textcolor;
final VoidCallback onTap;
final Color backgroundColor;
@ -544,9 +540,11 @@ class InfoCard extends StatelessWidget {
required this.value,
required this.dataset,
required this.bordercolor,
required this.colorPattern,
this.textcolor,
required this.onTap,
required this.backgroundColor,
required this.mainTopic,
}) : super(key: key);
@override
@ -554,11 +552,16 @@ class InfoCard extends StatelessWidget {
double myheight = MediaQuery.of(context).size.height;
double mywidth = MediaQuery.of(context).size.width;
print("colorPatternInfo -$colorPattern $mainTopic");
final encodedMainTopic = Uri.encodeComponent(mainTopic);
final encodedTitle = Uri.encodeComponent(title);
final encodedKey = Uri.encodeQueryComponent('home');
return GestureDetector(
onTap: () {
// Call handleInfoCardClick and pass the title and color
handleInfoCardClick(
context, dataset, bordercolor); // Pass 'title' to the function
context.go(
'/chartScreen/$dataset?bgColor=$colorPattern&mainTopic=$encodedMainTopic&title=$encodedTitle&key=$encodedKey');
},
child: Container(
height: myheight / 12,

View File

@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:pocketbase/pocketbase.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -57,6 +58,11 @@ class _BaseScaffoldState extends ConsumerState<BaseScaffold> {
_checkUserId();
}
Future<String> _getAppVersion() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
return packageInfo.version; // Returns the app version
}
Future<String?> getUserId() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('userId'); // Retrieve the userId
@ -183,98 +189,130 @@ class _BaseScaffoldState extends ConsumerState<BaseScaffold> {
),
),
drawer: Drawer(
child: ListView(
children: [
DrawerHeader(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
SizedBox(
width: mywidth / 8,
child: Image(
image: AssetImage('assets/logos/fcsc.png'),
),
),
],
),
Divider(),
InkWell(
onTap: () => context.go('/editProfile'),
child: Row(
child: Column(children: [
Expanded(
child: ListView(
children: [
DrawerHeader(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
SizedBox(
width: mywidth / 8,
height: mywidth / 8,
child: ClipOval(
child: _avatarUrl.isNotEmpty
? Image(
image: NetworkImage(_avatarUrl),
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
)
: Image(
image: AssetImage(
'assets/edit_profile/profile.png',
),
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
),
child: Image(
image: AssetImage('assets/logos/fcsc.png'),
),
//child: Image(image: AssetImage('assets/edit_profile/profile.png'))
),
SizedBox(
width: mywidth / 20,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Text(userId ?? 'Loading user...'), // Display userId here
// Text('Mohammad@fcsc.com')
Text(userName ?? 'Loading...'),
Text(
userEmail ?? 'Loading...',
style: TextStyle(fontSize: 12),
),
],
),
],
),
),
],
Divider(),
InkWell(
onTap: () { if(userId != 'guest')context.go('/editProfile');},
child: Row(
children: [
SizedBox(
width: mywidth / 8,
height: mywidth / 8,
child: ClipOval(
child: _avatarUrl.isNotEmpty
? Image(
image: NetworkImage(_avatarUrl),
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
)
: Image(
image: AssetImage(
'assets/edit_profile/profile.png',
),
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
),
),
//child: Image(image: AssetImage('assets/edit_profile/profile.png'))
),
SizedBox(
width: mywidth / 20,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Text(userId ?? 'Loading user...'), // Display userId here
// Text('Mohammad@fcsc.com')
if(userId == "guest")...[ Text(userName ?? 'Guest User'),]
else...[Text(userName ?? 'Loading...'),
Text(
userEmail ?? 'Loading...',
style: TextStyle(fontSize: 12),
),]
],
),
],
),
),
],
),
),
),
ListTile(
leading: Icon(Icons.feedback),
// title: const Text('Feedback'),
title: Text(AppLocalizations.of(context)!.feedback_title),
onTap: () => context.go('/feedback'),
),
if (role == 'admin')
if(userId != 'guest')
ListTile(
leading: Icon(Icons.manage_accounts),
title: Text('Manage User'),
// title: Text(
// AppLocalizations.of(context)!.manage_user,
// ),
onTap: () => context.go('/manageuser'),
leading: Icon(Icons.feedback),
// title: const Text('Feedback'),
title: Text(AppLocalizations.of(context)!.feedback_title),
onTap: () => context.go('/feedback'),
),
ListTile(
leading: Icon(Icons.book),
title: const Text('User Guide'),
onTap: () => context.go('/user-guide'),
),
ListTile(
leading: Icon(Icons.logout),
title: const Text('Logout'),
onTap: () => logout(),
),
],
if (role == 'admin')
ListTile(
leading: Icon(Icons.manage_accounts),
// title: Text('Manage User'),
title: Text(
AppLocalizations.of(context)!.manage_user,
),
onTap: () => context.go('/manageuser'),
),
ListTile(
leading: Icon(Icons.book),
title: const Text('User Guide'),
onTap: () => context.go('/user-guide'),
),
ListTile(
leading: Icon(Icons.logout),
title: const Text('Logout'),
onTap: () => logout(),
),
],
),
),
),
Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: FutureBuilder<String>(
future: _getAppVersion(), // Function to get the app version
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Text(
'Loading version...',
style: TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
);
} else if (snapshot.hasError) {
return Text(
'Error fetching version',
style: TextStyle(fontSize: 12, color: Colors.red),
textAlign: TextAlign.center,
);
} else {
return Text(
'Version ${snapshot.data}',
style: TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
);
}
},
),
),
])),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _getSelectedIndex(currentRoute),
onTap: (index) => _onItemTapped(context, index),

View File

@ -1,99 +1,100 @@
import 'package:flutter/material.dart';
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:video_player/video_player.dart';
import 'package:uae_stat/domain/use_cases/language.dart';
import 'package:uae_stat/infrastructure/services/img_asset_paths/user_guide_asset_path.dart';
import 'package:uae_stat/presentation/components/space.dart';
import 'package:uae_stat/presentation/components/themed_app_bar.dart';
typedef _UserGuideType = ({String videoPath, String enDesc, String arDesc});
class UserGuideRoute extends HookConsumerWidget {
const UserGuideRoute({super.key});
static const List<_UserGuideType> _data = [
(
videoPath: UserGuideAssetPath.toggleLanguage,
enDesc:
'You can toggle between Arabic and English by click the toggle button.',
arDesc:
'يمكنك التبديل بين العربية والإنجليزية عن طريق النقر على زر التبديل.',
),
(
videoPath: UserGuideAssetPath.searchIndicators,
enDesc:
'You can browse indicators by category or use the search functionality',
arDesc: 'يمكنك تصفح المؤشرات حسب الفئة أو استخدام وظيفة البحث',
),
(
videoPath: UserGuideAssetPath.bookmarkIndicators,
enDesc:
'You can bookmark indicators. You will be notified each time a bookmarked indicator is updated by email or mobile notifications.',
arDesc:
'يمكنك وضع إشارة مرجعية على المؤشرات. سيتم إعلامك في كل مرة يتم فيها تحديث المؤشر المرجعي عن طريق البريد الإلكتروني أو إشعارات الهاتف المحمول.',
),
];
@override
Widget build(BuildContext context, WidgetRef ref) {
final e = useState<_UserGuideType>(_data.first);
final carouselOptions = CarouselOptions(
autoPlay: true,
clipBehavior: Clip.none,
onPageChanged: (index, reason) => e.value = _data[index],
aspectRatio: 9 / 17,
disableCenter: true,
enlargeCenterPage: true,
floatingIndicator: true,
autoPlayInterval: const Duration(seconds: 10),
);
final bodyContent = Column(
children: [
Expanded(
flex: 3,
child: FlutterCarousel(
options: carouselOptions,
items: _data
.map(
(e) => VideoPlayer(
VideoPlayerController.asset(
e.videoPath,
)..initialize(),
),
)
.toList(),
),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(36, 24, 36, 36),
child: Text(
context.translate(e.value.enDesc, e.value.arDesc),
),
),
),
98.verticalSpace,
],
);
final body = Column(
children: [
ThemedAppBar(
titleText: context.translate(
'User Guide',
'دليل المستخدم',
),
),
Expanded(
child: bodyContent,
),
],
);
return ColoredBox(
color: Colors.white,
child: SafeArea(child: body),
);
}
}
// import 'package:flutter/material.dart';
// import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
// import 'package:flutter_hooks/flutter_hooks.dart';
// import 'package:hooks_riverpod/hooks_riverpod.dart';
// import 'package:video_player/video_player.dart';
//
// import 'package:uae_stat/domain/use_cases/language.dart';
// import 'package:uae_stat/infrastructure/services/img_asset_paths/user_guide_asset_path.dart';
// import 'package:uae_stat/presentation/components/space.dart';
// import 'package:uae_stat/presentation/components/themed_app_bar.dart';
//
// typedef _UserGuideType = ({String videoPath, String enDesc, String arDesc});
//
// class UserGuideRoute extends HookConsumerWidget {
// const UserGuideRoute({super.key});
//
// static const List<_UserGuideType> _data = [
// (
// videoPath: UserGuideAssetPath.toggleLanguage,
// enDesc:
// 'You can toggle between Arabic and English by click the toggle button.',
// arDesc:
// 'يمكنك التبديل بين العربية والإنجليزية عن طريق النقر على زر التبديل.',
// ),
// (
// videoPath: UserGuideAssetPath.searchIndicators,
// enDesc:
// 'You can browse indicators by category or use the search functionality',
// arDesc: 'يمكنك تصفح المؤشرات حسب الفئة أو استخدام وظيفة البحث',
// ),
// (
// videoPath: UserGuideAssetPath.bookmarkIndicators,
// enDesc:
// 'You can bookmark indicators. You will be notified each time a bookmarked indicator is updated by email or mobile notifications.',
// arDesc:
// 'يمكنك وضع إشارة مرجعية على المؤشرات. سيتم إعلامك في كل مرة يتم فيها تحديث المؤشر المرجعي عن طريق البريد الإلكتروني أو إشعارات الهاتف المحمول.',
// ),
// ];
//
// @override
// Widget build(BuildContext context, WidgetRef ref) {
// final e = useState<_UserGuideType>(_data.first);
// final carouselOptions
// // final carouselOptions = CarouselOptions(
// // autoPlay: true,
// // clipBehavior: Clip.none,
// // onPageChanged: (index, reason) => e.value = _data[index],
// // aspectRatio: 9 / 17,
// // disableCenter: true,
// // enlargeCenterPage: true,
// // floatingIndicator: true,
// // autoPlayInterval: const Duration(seconds: 10),
// // );
// final bodyContent = Column(
// children: [
// Expanded(
// flex: 3,
// child: FlutterCarousel(
// options: carouselOptions,
// items: _data
// .map(
// (e) => VideoPlayer(
// VideoPlayerController.asset(
// e.videoPath,
// )..initialize(),
// ),
// )
// .toList(),
// ),
// ),
// Expanded(
// child: SingleChildScrollView(
// padding: const EdgeInsets.fromLTRB(36, 24, 36, 36),
// child: Text(
// context.translate(e.value.enDesc, e.value.arDesc),
// ),
// ),
// ),
// 98.verticalSpace,
// ],
// );
// final body = Column(
// children: [
// ThemedAppBar(
// titleText: context.translate(
// 'User Guide',
// 'دليل المستخدم',
// ),
// ),
// Expanded(
// child: bodyContent,
// ),
// ],
// );
// return ColoredBox(
// color: Colors.white,
// child: SafeArea(child: body),
// );
// }
// }