From 066235889bc1c641fc60f143ce89e07e27cc7158 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Tue, 7 Jan 2025 09:35:28 +0530 Subject: [PATCH 1/3] charts added --- .run/main.dart.run.xml | 2 +- lib/config/my_router.dart | 22 +- lib/presentation/Screens/charts/chart.dart | 1225 +++++++++++++++++ lib/presentation/Screens/demo_home.dart | 352 +++-- lib/presentation/Screens/demo_home2.dart | 328 +++++ .../tab_routes/home_route.dart | 53 +- pubspec.yaml | 2 +- 7 files changed, 1873 insertions(+), 111 deletions(-) create mode 100644 lib/presentation/Screens/charts/chart.dart create mode 100644 lib/presentation/Screens/demo_home2.dart diff --git a/.run/main.dart.run.xml b/.run/main.dart.run.xml index 4767aff8..4a58a7c9 100644 --- a/.run/main.dart.run.xml +++ b/.run/main.dart.run.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/lib/config/my_router.dart b/lib/config/my_router.dart index a4e4693f..da605ad4 100644 --- a/lib/config/my_router.dart +++ b/lib/config/my_router.dart @@ -295,6 +295,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:uae_stat/presentation/Screens/auth_verification/registration.dart'; +import 'package:uae_stat/presentation/Screens/demo_home2.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; import '../domain/use_cases/preferences_use_case.dart'; @@ -302,6 +303,7 @@ import '../presentation/Screens/auth_verification/changepassword.dart'; import '../presentation/Screens/auth_verification/confirm_password.dart'; import '../presentation/Screens/auth_verification/create_new_pw.dart'; import '../presentation/Screens/auth_verification/otp_verification.dart'; +import '../presentation/Screens/charts/chart.dart'; import '../presentation/Screens/demo_home.dart'; import '../presentation/Screens/profilepage.dart'; import '../presentation/routes/auth_routes/login_route.dart'; @@ -313,13 +315,12 @@ final GoRouter router = GoRouter( routes: [ GoRoute( path: '/', -builder: (context, state) => LoginRoute(), + builder: (context, state) => LoginRoute(), //builder: (context, state) => MyHomePage(), ), GoRoute( path: '/login', builder: (context, state) => LoginRoute(), - ), GoRoute( path: '/myhomepage', @@ -330,8 +331,20 @@ builder: (context, state) => LoginRoute(), builder: (context, state) => RegisterScreen(), ), GoRoute( - path: '/DemoHome', - builder: (context, state) => ChartPage(), + path: '/DemoHome/:dataSets', + builder: (context, state) { + final dataSets = state.pathParameters['dataSets']!; + print('Router $dataSets'); + return ChartPage(dataSets: dataSets); + }, + ), + GoRoute( + path: '/Chart/:dataSets', + builder: (context, state) { + final dataSets = state.pathParameters['dataSets']!; + print('Router $dataSets'); + return ChartScreen(dataSets: dataSets); + }, ), GoRoute( path: '/mailverification', @@ -390,4 +403,3 @@ builder: (context, state) => LoginRoute(), ), ], ); - diff --git a/lib/presentation/Screens/charts/chart.dart b/lib/presentation/Screens/charts/chart.dart new file mode 100644 index 00000000..1911ddee --- /dev/null +++ b/lib/presentation/Screens/charts/chart.dart @@ -0,0 +1,1225 @@ +import 'dart:collection'; +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:intl/intl.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; // Import Syncfusion charts package +import 'package:http/http.dart' as http; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: ChartScreen(dataSets: ''), + ); + } +} + +class ChartScreen extends StatefulWidget { + final String dataSets; + const ChartScreen({Key? key, required this.dataSets}); + @override + ChartScreenState createState() => ChartScreenState(); +} + +class ChartScreenState extends State { + List> selectedFiltersStorage = []; + List chartsData = []; + List cardData = []; + List filterData = []; + List> originalChartsData = []; + List> originalCardData = []; + bool isLoading = true; + List isChartData = []; + List nonChartData = []; + // dynamic firstYear; + // dynamic lastYearValue; + // dynamic secondLastYearValue; + // dynamic lastYear; + // dynamic secondLastYear; + // dynamic roundedAverage; + final GlobalKey _scaffoldKey = GlobalKey(); + + @override + void initState() { + super.initState(); + fetchChartData(widget.dataSets); + } + + Future fetchChartData(String dataSets) async { + const baseUrl = + 'https://pb.venbait.in/api/getDataSet'; // Replace with your server URL + final url = Uri.parse('$baseUrl?dataset=$dataSets'); + + try { + final response = await http.get(url); + if (response.statusCode == 200) { + final List data = jsonDecode(response.body); + + // Variables to store filtered data + for (var item in data) { + if (item['is_chart'] == 'true') { + isChartData.add(item); + } else if (item['is_chart'] == 'false') { + nonChartData.add(item); + } + } + + // Print or use the filtered data + print('Chart Data: $isChartData'); + print('Non-Chart Data: $nonChartData'); + + // Save original data for reset + originalChartsData = List.from(isChartData); // Store original data + originalCardData = List.from(nonChartData); // Store original data + setState(() { + chartsData = isChartData; + cardData = nonChartData; + print('chartsData $chartsData'); + print('cardData $cardData'); + isLoading = false; + }); + } else { + throw Exception('Failed to load data'); + } + } catch (error) { + setState(() { + isLoading = false; + }); + print('Error fetching data: $error'); + } + } + + // void extractLastTwoYearsData(Map nonChartData) { + // try { + // final responseData = nonChartData['response']; + // final response = responseData is String + // ? jsonDecode(responseData) as List + // : responseData as List; + // + // // Extracting the time periods and sorting them in descending order + // final sortedResponse = response + // .map((item) => item['ObsKey']['TIME_PERIOD'].toString()) + // .toList() + // ..sort((a, b) => b.compareTo(a)); + // + // // Get the last year and second-to-last year + // lastYear = sortedResponse.isNotEmpty ? sortedResponse.first : null; + // secondLastYear = sortedResponse.length > 1 ? sortedResponse[1] : null; + // firstYear = sortedResponse.isNotEmpty + // ? sortedResponse.last + // : null; // First year (oldest) + // + // // Extract the corresponding values + // lastYearValue = response.firstWhere( + // (item) => item['ObsKey']['TIME_PERIOD'].toString() == lastYear, + // orElse: () => null)?['ObsValue']['Value']; + // + // secondLastYearValue = response.firstWhere( + // (item) => item['ObsKey']['TIME_PERIOD'].toString() == secondLastYear, + // orElse: () => null)?['ObsValue']['Value']; + // + // // Print or use the extracted values + // print('Last Year: $lastYear, Value: $lastYearValue'); + // print( + // 'Second-to-Last Year: $secondLastYear, Value: $secondLastYearValue'); + // + // // Extract all values and calculate the sum and average + // List allValues = response + // .map((item) => + // double.tryParse(item['ObsValue']['Value'].toString()) ?? 0.0) + // .toList(); + // + // double sum = allValues.reduce((a, b) => a + b); + // double average = sum / allValues.length; + // // Round the average to the nearest whole number + // roundedAverage = average.round(); + // + // // Print sum and rounded average + // print('Sum of Values: $sum'); + // print('Rounded Average Value: $roundedAverage'); + // } catch (e) { + // print('Error processing data: $e'); + // } + // } + +// When decoding JSON, you can convert the LinkedMap to a Map + Map processNonChartData(item) { + print('processNonChartData check test case'); + try { + final response = item['response']; + + // If 'response' is a LinkedMap, convert it to Map + final List responseData = response is LinkedHashMap + ? Map.from( + response) // Convert to Map + : response; + + // Now you can process the Map as expected + final sortedResponse = List.from( + responseData.map((e) => e['ObsKey']['TIME_PERIOD'].toString())) + ..sort((a, b) => b.compareTo(a)); + + String? lastYear = + sortedResponse.isNotEmpty ? sortedResponse.first : null; + String? secondLastYear = + sortedResponse.length > 1 ? sortedResponse[1] : null; + String? firstYear = + sortedResponse.isNotEmpty ? sortedResponse.last : null; + + String? lastYearValue = response.firstWhere( + (item) => item['ObsKey']['TIME_PERIOD'].toString() == lastYear, + orElse: () => null, + )?['ObsValue']['Value']; + + String? secondLastYearValue = response.firstWhere( + (item) => item['ObsKey']['TIME_PERIOD'].toString() == secondLastYear, + orElse: () => null, + )?['ObsValue']['Value']; + + List allValues = responseData + .map((item) => + double.tryParse(item['ObsValue']['Value'].toString()) ?? 0.0) + .toList(); + + double sum = allValues.reduce((a, b) => a + b); + double average = sum / allValues.length; + double roundedAverage = + average.roundToDouble(); // Rounds to the nearest integer + + return { + 'lastYear': lastYear ?? 'NA', + 'secondLastYear': secondLastYear ?? 'NA', + 'firstYear': firstYear ?? 'NA', + 'lastYearValue': lastYearValue ?? 'NA', + 'secondLastYearValue': secondLastYearValue ?? 'NA', + 'roundedAverage': roundedAverage, + 'allValues': allValues.isEmpty ? [0.0] : allValues, + }; + } catch (e) { + print('Error processing data: $e'); + return {}; + } + } + + String capitalizeAndSplit(String text) { + if (text.isEmpty) return text; + return text + .split('_') // Split by underscores + .map((word) => word[0].toUpperCase() + word.substring(1).toLowerCase()) + .join(' '); // Join words with a space + } + + List parseLineChartData(List response) { + return response.map((entry) { + // Attempt to fetch the TIMEPERIOD + final rawTimePeriod = + entry['ObsKey']?['Year'] ?? entry['ObsKey']?['TIME_PERIOD']; + final formattedTimePeriod = rawTimePeriod != null + ? rawTimePeriod.toString() // Use as-is if valid + : 'Unknown'; // Fallback value if null + + // Parse the value or fallback to 0.0 + final value = + double.tryParse(entry['ObsValue']?['Value']?.toString() ?? '0.0') ?? + 0.0; + + return ChartData( + timePeriod: formattedTimePeriod, + value: value, + ); + }).toList(); + } + + List parseBarChartData(List response) { + return response + .asMap() + .entries + .map((entry) => ChartData( + timePeriod: entry.value['ObsKey']['TIME_PERIOD'] ?? + 'Unknown', // Fallback to 'Unknown' if null + value: double.tryParse( + entry.value['ObsValue']['Value'].toString()) ?? + 0.0, // Handle null/invalid value + )) + .toList(); + } + +// Update parseColumnChartData for column chart + List parseColumnChartData(List response) { + return response.map((entry) { + // Extract TIME_PERIOD as x + final x = + int.tryParse(entry['ObsKey']?['TIME_PERIOD']?.toString() ?? '0') ?? 0; + // Extract Value as y + final y = + double.tryParse(entry['ObsValue']?['Value']?.toString() ?? '0.0') ?? + 0.0; + return ChartData(xInt: x, y: y); + }).toList(); + } + +// Update parseScatterChartData for scatter chart + List parseScatterChartData(List response) { + return response.map((entry) { + // Parse TIME_PERIOD into DateTime + final timePeriod = entry['ObsKey']?['TIME_PERIOD']; + final x = timePeriod != null + ? DateTime.tryParse('$timePeriod-01-01') // Convert year to DateTime + : DateTime.now(); // Fallback to current date if null + + // Parse Value into double + final y = + double.tryParse(entry['ObsValue']?['Value']?.toString() ?? '0.0') ?? + 0.0; + + return ChartData(xDateTime: x, y: y); + }).toList(); + } + + List parseStackedBarChartData( + dynamic chartData, String groupByKey, String groupByValue) { + return chartData['response'] + .where((entry) => entry['ObsKey'][groupByKey] == groupByValue) + .map((entry) { + // Specify the type here + return ChartData( + x: entry['ObsKey']['TIME_PERIOD'], + y: double.tryParse(entry['ObsValue']['Value']) ?? 0.0, + ); + }).toList(); + } + + 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 + if (selectedFilters.every((filter) => filter['filter_data'].isEmpty)) { + setState(() { + chartsData = List.from(originalChartsData); // Restore the original data + cardData = List.from(originalCardData); // Restore the original data + }); + print('Returning as no filters are selected'); + Navigator.pop(context); + return; // Exit early since all filter_data are empty + } + + // Loop through each chart data in the `data` list + List filteredData = []; + for (var chart in data) { + // Extract response data for filtering + List response = chart['response'] ?? []; + + // Filter the response based on selected filters + var chartFilteredData = response.where((responseItem) { + final obsKey = responseItem['ObsKey']; + + // Check if each selected filter's `filter_data` matches `ObsKey` values + return selectedFilters.every((filter) { + final filterKey = filter['filter_key']; + 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)) { + final obsKeyValue = obsKey[filterKey]?.toString(); + return filterValues.isEmpty || filterValues.contains(obsKeyValue); + } + return false; + }); + }).toList(); + + // If any data matches the filter, add the whole chart data object + if (chartFilteredData.isNotEmpty) { + filteredData.add({ + ...chart, // Include all other properties of the chart object + 'response': chartFilteredData, // Only include filtered response data + }); + } + } + + List filteredCardData = []; + for (var chart in dataCard) { + Map chartData = Map.from(chart); + // Extract response data for filtering + List response = chartData['response'] ?? []; + + // Filter the response based on selected filters + var cardFilteredData = response.where((responseItem) { + final obsKey = responseItem['ObsKey']; + + // Check if each selected filter's `filter_data` matches `ObsKey` values + return selectedFilters.every((filter) { + final filterKey = filter['filter_key']; + 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)) { + final obsKeyValue = obsKey[filterKey]?.toString(); + return filterValues.isEmpty || filterValues.contains(obsKeyValue); + } + return false; + }); + }).toList(); + + // If any data matches the filter, add the whole chart data object + if (cardFilteredData.isNotEmpty) { + filteredCardData.add({ + ...chart, // Include all other properties of the chart object + 'response': cardFilteredData, // Only include filtered response data + }); + } + } + + // Update the chartsData with the filtered data + setState(() { + chartsData = filteredData; + cardData = filteredCardData; + }); + + print("Filtered Data: $filteredData"); + print("Filtered Card: $filteredCardData"); + + // Go back after applying filters + Navigator.pop(context); + } + + Widget buildChart(dynamic chartData) { + // Extract group_by dynamically from the chartData + String groupByKey = chartData['group_by']; + + // Extract unique values for the group_by key dynamically + Set groupByValues = {}; + chartData['response'].forEach((entry) { + groupByValues.add(entry['ObsKey'][groupByKey]); + }); + switch (chartData['chart_type']) { + case 'scatter': + return SfCartesianChart( + primaryXAxis: DateTimeAxis(), // Ensure it's DateTimeAxis + tooltipBehavior: TooltipBehavior(enable: true), + title: ChartTitle(text: 'Divorces'), + series: [ + ScatterSeries( + dataSource: parseScatterChartData(chartData['response']), + xValueMapper: (ChartData data, _) => + data.xDateTime ?? DateTime.now(), + yValueMapper: (ChartData data, _) => data.y, + name: chartData['dataset'], + dataLabelSettings: DataLabelSettings(isVisible: true), + ), + ], + ); + case 'column': + return SfCartesianChart( + primaryXAxis: NumericAxis( + title: AxisTitle(text: 'Year'), + interval: 1, // Ensure no fractional intervals + ), + title: ChartTitle(text: 'Marriages'), + series: >[ + ColumnSeries( + dataSource: parseColumnChartData(chartData['response']), + xValueMapper: (ChartData data, _) => data.xInt ?? 0, + yValueMapper: (ChartData data, _) => data.y, + name: chartData['dataset'], + color: Color(0xFF7DAFBC), + width: 0.8, + spacing: 0.2, + dataLabelSettings: + DataLabelSettings(isVisible: true), // Show data labels + ), + ], + ); + case 'line': + return SfCartesianChart( + primaryXAxis: CategoryAxis(), + title: ChartTitle(text: 'Line Chart'), + legend: Legend(isVisible: true), + tooltipBehavior: TooltipBehavior(enable: true), + series: >[ + LineSeries( + dataSource: parseLineChartData(chartData['response']), + xValueMapper: (ChartData data, _) => data.timePeriod ?? '', + yValueMapper: (ChartData data, _) => data.value ?? 0.0, + name: 'Sales', + dataLabelSettings: DataLabelSettings(isVisible: true), + ), + ], + ); + case 'bar': + return SfCartesianChart( + primaryXAxis: CategoryAxis(), + title: ChartTitle(text: 'Bar Chart'), + tooltipBehavior: TooltipBehavior(enable: true), + series: >[ + BarSeries( + dataSource: parseBarChartData(chartData['response']), + xValueMapper: (ChartData data, _) => data.timePeriod ?? '', + yValueMapper: (ChartData data, _) => data.value ?? 0.0, + name: 'Gold', + color: Color.fromRGBO(8, 142, 255, 1), + ), + ], + ); + case 'stacked_bar': + String chartTitle = + capitalizeAndSplit(chartData['chart_heading'] ?? ''); + // Define a list of colors + final List uniqueColors = [ + Color(0xFF648CBA), + Color(0xFF90B0D5), + Color(0xFF98BCE5), + Color(0xFFA7B5C5), + Color(0xFFBED3EC), + Color(0xFFD4E3F4), + ]; + return SfCartesianChart( + primaryXAxis: CategoryAxis(), + title: ChartTitle(text: chartData['chart_heading']), + legend: Legend( + isVisible: true, + position: LegendPosition.bottom, + overflowMode: LegendItemOverflowMode.scroll, + ), + tooltipBehavior: TooltipBehavior( + enable: true, // Enable tooltips + format: 'point.x : point.y', // Custom tooltip format + ), + series: >[ + for (int i = 0; i < groupByValues.length; i++) + StackedBarSeries( + dataSource: parseStackedBarChartData( + chartData, groupByKey, groupByValues.elementAt(i)), + xValueMapper: (ChartData data, _) => data.x ?? '', + yValueMapper: (ChartData data, _) => data.y, + name: groupByValues.elementAt(i), + color: uniqueColors[i % uniqueColors.length], + enableTooltip: true, + ), + ], + ); + case 'stacked_column': + String chartTitle = + capitalizeAndSplit(chartData['chart_heading'] ?? ''); + // Define a list of colors + final List uniqueColors = [ + Color(0xFF648CBA), + Color(0xFF90B0D5), + Color(0xFF98BCE5), + Color(0xFFA7B5C5), + Color(0xFFBED3EC), + Color(0xFFD4E3F4), + ]; + return SfCartesianChart( + primaryXAxis: CategoryAxis(), + title: ChartTitle(text: chartData['chart_heading']), + legend: Legend( + isVisible: true, + position: LegendPosition.bottom, + overflowMode: LegendItemOverflowMode.scroll, + ), + tooltipBehavior: TooltipBehavior( + enable: true, // Enable tooltips + format: 'point.x : point.y', // Custom tooltip format + ), + series: >[ + for (int i = 0; i < groupByValues.length; i++) + StackedColumnSeries( + dataSource: parseStackedBarChartData( + chartData, groupByKey, groupByValues.elementAt(i)), + xValueMapper: (ChartData data, _) => data.x ?? '', + yValueMapper: (ChartData data, _) => data.y, + name: groupByValues.elementAt(i), + color: uniqueColors[i % uniqueColors.length], + enableTooltip: true, + ), + ], + ); + default: + return Center(child: Text('Unknown chart type')); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: _scaffoldKey, + backgroundColor: Colors.brown[200]!, + appBar: AppBar( + backgroundColor: Colors.brown[200]!, + elevation: 0, + leading: IconButton( + icon: Icon(Icons.arrow_back_ios_new, color: Colors.white), + onPressed: () { + context.go('/myhomepage'); + }, + ), + title: Text( + 'UAE Numbers', + style: TextStyle(color: Colors.white), + ), + ), + body: isLoading + ? Center(child: CircularProgressIndicator()) + : Column( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment + .end, // Aligns the row's content to the right + children: [ + Row( + children: [ + Text( + 'Bookmark', + style: const TextStyle( + fontSize: 16, + color: Colors.white, + ), + ), + Icon(Icons.bookmark_add_outlined, + color: Colors.white, size: 18), + ], + ), + SizedBox(width: 10), // Horizontal space between items + Row( + children: [ + Text( + 'Share', + style: const TextStyle( + fontSize: 16, + color: Colors.white, + ), + ), + Icon(Icons.share_sharp, + color: Colors.white, size: 18), + ], + ), + SizedBox(width: 10), // Horizontal space between items + IconButton( + icon: Icon(Icons.filter_alt_outlined, + color: Colors.white, size: 18), + onPressed: () { + showRightSideModal( + context, chartsData[0]["filters"], chartsData); + }, + ), + ], + ), + ], + ), + + // Padding( + // padding: const EdgeInsets.all(16.0), + // child: Align( + // alignment: Alignment.topRight, + // child: IconButton( + // icon: Icon(Icons.filter_list, color: Colors.white), + // onPressed: () { + // showRightSideModal( + // context, chartsData[0]["filters"], chartsData); + // }, + // ), + // ), + // ), + Expanded( + child: ListView( + children: [ + // nonChartData Cards + GridView.builder( + itemCount: cardData.length, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, // 2 cards per row + crossAxisSpacing: 10, + mainAxisSpacing: 10, + childAspectRatio: 177.5 / + 180, // Adjusted to maintain width and fixed height + ), + 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 data = processNonChartData(item); + print('processNonChartData'); + + if (chart_type == 'total') { + return Card( + margin: const EdgeInsets.all(10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 4, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.public, + color: Color(0xFF90B0D5), size: 22), + const SizedBox(height: 5), + Text( + '${chart_heading ?? 'NA'}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + Text( + '(${data['lastYear'] ?? 'NA'})', + style: const TextStyle( + fontSize: 10, color: Colors.grey), + ), + Text( + '${data['lastYearValue'] ?? 'NA'}', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Color(0xFF90B0D5), + ), + ), + const Divider( + color: Colors.grey, thickness: 1), + Text( + '${data['secondLastYearValue'] ?? 'NA'}', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Color(0xFF90B0D5), + ), + ), + Text( + '(${data['secondLastYear'] ?? 'NA'})', + style: const TextStyle( + fontSize: 10, color: Colors.grey), + ), + ], + ), + ), + ); + } else if (chart_type == 'average') { + return Card( + margin: const EdgeInsets.all(10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 4, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.analytics, + color: Color(0xFF90B0D5), size: 22), + const SizedBox(height: 5), + Text( + '${chart_heading ?? 'NA'}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + Text( + '(${data['firstYear'] ?? 'NA'} - ${data['lastYear'] ?? 'NA'})', + style: const TextStyle( + fontSize: 10, color: Colors.grey), + ), + Text( + '${data['roundedAverage'] ?? 'NA'}', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Color(0xFF90B0D5), + ), + ), + ], + ), + ), + ); + } else { + return const SizedBox + .shrink(); // Ignore unknown KPIs + } + }, + ), + // chartsData Cards + ListView.builder( + itemCount: chartsData.length, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return Card( + margin: const EdgeInsets.all(10), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + Container( + height: 300, + child: buildChart(chartsData[index]), + ), + ], + ), + ), + ); + }, + ), + ], + ), + ), + ], + ), + ); + } + + void showRightSideModal(BuildContext context, List filters, List data) { + // Initialize selected filters structure from the stored value, if exists + List> selectedFilters = + selectedFiltersStorage.isNotEmpty + ? List.from(selectedFiltersStorage) // Use stored filters + : filters.map((filter) { + return {"filter_key": filter["filter_key"], "filter_data": []}; + }).toList(); // Or initialize empty filters + + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return Align( + alignment: Alignment.centerRight, + child: Container( + width: MediaQuery.of(context).size.width * 0.7, + height: MediaQuery.of(context).size.height, + color: Colors.white, + child: Column( + children: [ + // Header Section + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Filters', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + IconButton( + icon: Icon(Icons.close), + onPressed: () => Navigator.pop(context), + ), + ], + ), + ), + // Filters Section + Expanded( + child: ListView.builder( + itemCount: filters.length, + itemBuilder: (context, index) { + final filter = filters[index]; + final filterKey = filter["filter_key"]; + final filterData = filter["filter_data"]; + + return StatefulBuilder( + builder: (context, setState) { + // Get the corresponding selected filter object + var selectedFilter = selectedFilters + .firstWhere((f) => f["filter_key"] == filterKey); + + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + filterKey, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + InkWell( + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, dialogSetState) { + return AlertDialog( + title: Text('Select $filterKey'), + content: SingleChildScrollView( + child: ListBody( + children: filterData + .map((value) { + return CheckboxListTile( + title: Text( + value.toString()), + value: selectedFilter[ + "filter_data"] + .contains(value), + onChanged: + (bool? isChecked) { + dialogSetState(() { + if (isChecked == + true) { + selectedFilter[ + "filter_data"] + .add(value); + } else { + selectedFilter[ + "filter_data"] + .remove(value); + } + }); + }, + ); + }).toList(), + ), + ), + actions: [ + TextButton( + onPressed: () { + // Store the updated selected filters after dialog closes + setState(() {}); + Navigator.pop(context); + }, + child: Text('OK'), + ), + ], + ); + }, + ); + }, + ); + }, + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric( + horizontal: 16.0, vertical: 12.0), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(8.0), + ), + child: Wrap( + spacing: 8.0, + runSpacing: 4.0, + children: + selectedFilter["filter_data"].isEmpty + ? [ + Text('Select $filterKey', + style: TextStyle( + color: Colors.grey)) + ] + : selectedFilter["filter_data"] + .map((value) { + return Chip( + label: Text(value), + onDeleted: () { + setState(() { + selectedFilter[ + "filter_data"] + .remove(value); + }); + }, + ); + }).toList(), + ), + ), + ), + ], + ), + ); + }, + ); + }, + ), + ), + // Button Section + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () { + // Clear all selected filters + selectedFilters.forEach((filter) { + filter["filter_data"].clear(); + }); + setState(() { + chartsData = List.from(originalChartsData); + cardData = List.from(originalCardData); + }); + // Reset the selectedFiltersStorage to empty when clearing + selectedFiltersStorage.clear(); + Navigator.pop(context); + }, + child: Text('Clear'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.grey, + ), + ), + ElevatedButton( + onPressed: () { + print( + "Selected Filters before applying: $selectedFilters"); + setState(() { + chartsData = originalChartsData; + cardData = originalCardData; + }); + applyFilters(context, filters, chartsData, cardData, + selectedFilters); + // Save the selected filters to storage after applying + selectedFiltersStorage = List.from(selectedFilters); + }, + child: Text('Apply Filter'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + ), + ), + ], + ), + ), + ], + ), + ), + ); + }, + ); + } +} + +class ChartData { + final int? xInt; // For column and other charts, using int for x + final DateTime? xDateTime; // For scatter charts, using DateTime for x + final double? y; // For column and other charts + final String? timePeriod; // For line and bar charts + final double? value; // For line and bar charts + late final double? y2; // For secondary data (stacked column chart) + late final double? y3; // For tertiary data (stacked column chart) + final double? y4; // For quaternary data (stacked column chart) + final String? x; // For stacked bar charts, using String for x + + // Constructor that handles all chart cases + ChartData({ + this.xInt, + this.xDateTime, + this.y, + this.timePeriod, + this.value, + this.y2, + this.y3, + this.y4, + this.x, + }); +} + +// [ +// { +// chart_type :"scatter" +// dataset :"divorces" +// is_chart :"true" +// kpi:"divorces" +// filters:[ +// { +// "filter_key": "REF_AREA", +// "filter_data": [ +// "AE", +// "AE-AJ", +// "AE-RK", +// "AE-FJ", +// "AE-AZ", +// "AE-DU", +// "AE-SH", +// "AE-UQ" +// ] +// }, +// { +// "filter_key": "TIME_PERIOD", +// "filter_data": [ +// "2021", +// "2017", +// "2018", +// "2019", +// "2020" +// ] +// }, +// { +// "filter_key": "DV_TYPE", +// "filter_data": [ +// "D_EM_NEF", +// "D_EM_EF", +// "D_NEW_NEF", +// "D_TOT", +// "D_NEM_EF" +// ] +// } +// ] +// group_by:"DV_TYPE" +// main_id :"social" +// response:[ +// { +// "ObsKey": { +// "DV_TYPE": "D_TOT", +// "FREQ": "A", +// "MEASURE": "D", +// "REF_AREA": "AE-DU", +// "SOURCE_DETAIL": "FCSAMD", +// "TIME_PERIOD": "2017", +// "UNIT_MEASURE": "NUMBER" +// }, +// "ObsValue": { +// "Value": "1484" +// } +// }, +// { +// "ObsKey": { +// "DV_TYPE": "D_TOT", +// "FREQ": "A", +// "MEASURE": "D", +// "REF_AREA": "AE-DU", +// "SOURCE_DETAIL": "FCSAMD", +// "TIME_PERIOD": "2019", +// "UNIT_MEASURE": "NUMBER" +// }, +// "ObsValue": { +// "Value": "1308" +// } +// }, +// { +// "ObsKey": { +// "DV_TYPE": "D_TOT", +// "FREQ": "A", +// "MEASURE": "D", +// "REF_AREA": "AE-DU", +// "SOURCE_DETAIL": "FCSAMD", +// "TIME_PERIOD": "2017", +// "UNIT_MEASURE": "NUMBER" +// }, +// "ObsValue": { +// "Value": "1484" +// } +// }, +// { +// "ObsKey": { +// "DV_TYPE": "D_TOT", +// "FREQ": "A", +// "MEASURE": "D", +// "REF_AREA": "AE-DU", +// "SOURCE_DETAIL": "FCSAMD", +// "TIME_PERIOD": "2018", +// "UNIT_MEASURE": "NUMBER" +// }, +// "ObsValue": { +// "Value": "1321" +// } +// }, +// { +// "ObsKey": { +// "DV_TYPE": "D_NEM_EF", +// "FREQ": "A", +// "MEASURE": "D", +// "REF_AREA": "AE", +// "SOURCE_DETAIL": "FCSAMD", +// "TIME_PERIOD": "2017", +// "UNIT_MEASURE": "NUMBER" +// }, +// "ObsValue": { +// "Value": "181" +// } +// }, +// { +// "ObsKey": { +// "DV_TYPE": "D_NEM_EF", +// "FREQ": "A", +// "MEASURE": "D", +// "REF_AREA": "AE", +// "SOURCE_DETAIL": "FCSAMD", +// "TIME_PERIOD": "2019", +// "UNIT_MEASURE": "NUMBER" +// }, +// "ObsValue": { +// "Value": "182" +// } +// }, +// { +// "ObsKey": { +// "DV_TYPE": "D_EM_NEF", +// "FREQ": "A", +// "MEASURE": "D", +// "REF_AREA": "AE", +// "SOURCE_DETAIL": "FCSAMD", +// "TIME_PERIOD": "2017", +// "UNIT_MEASURE": "NUMBER" +// }, +// "ObsValue": { +// "Value": "766" +// } +// }, +// { +// "ObsKey": { +// "DV_TYPE": "D_EM_NEF", +// "FREQ": "A", +// "MEASURE": "D", +// "REF_AREA": "AE", +// "SOURCE_DETAIL": "FCSAMD", +// "TIME_PERIOD": "2020", +// "UNIT_MEASURE": "NUMBER" +// }, +// "ObsValue": { +// "Value": "618" +// } +// }, +// { +// "ObsKey": { +// "DV_TYPE": "D_EM_EF", +// "FREQ": "A", +// "MEASURE": "D", +// "REF_AREA": "AE", +// "SOURCE_DETAIL": "FCSAMD", +// "TIME_PERIOD": "2017", +// "UNIT_MEASURE": "NUMBER" +// }, +// "ObsValue": { +// "Value": "1717" +// } +// }, +// { +// "ObsKey": { +// "DV_TYPE": "D_EM_EF", +// "FREQ": "A", +// "MEASURE": "D", +// "REF_AREA": "AE", +// "SOURCE_DETAIL": "FCSAMD", +// "TIME_PERIOD": "2018", +// "UNIT_MEASURE": "NUMBER" +// }, +// "ObsValue": { +// "Value": "1720" +// } +// } +// +// ] +// sub_id :"vital_statistics" +// url :"https://releaseeuaestat.fcsc.gov.ae/rest/data/FCSA,DF_DV_NA,1.5.0/.A....?startPeriod=2017&endPeriod=2024&dimensionAtObservation=AllDimensions" +// } +// ] diff --git a/lib/presentation/Screens/demo_home.dart b/lib/presentation/Screens/demo_home.dart index 225f1b1b..895a5a29 100644 --- a/lib/presentation/Screens/demo_home.dart +++ b/lib/presentation/Screens/demo_home.dart @@ -13,12 +13,15 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - home: ChartPage(), + home: ChartPage(dataSets: ''), ); } } class ChartPage extends StatefulWidget { + final String dataSets; + const ChartPage({Key? key, required this.dataSets}); + @override ChartPageState createState() => ChartPageState(); } @@ -30,13 +33,13 @@ class ChartPageState extends State { @override void initState() { super.initState(); - fetchChartData(); + fetchChartData(widget.dataSets); } - Future fetchChartData() async { + Future fetchChartData(datasets) async { const baseUrl = 'https://pb.venbait.in/api/custom/apicall'; // Replace with your server URL - final url = Uri.parse('$baseUrl?dataset=hotels'); + final url = Uri.parse('$baseUrl?dataset=$datasets'); try { final response = await http.get(url); @@ -57,24 +60,34 @@ class ChartPageState extends State { } List parseLineChartData(List response) { - return response.map((entry) { - // Attempt to fetch the TIMEPERIOD - final rawTimePeriod = - entry['ObsKey']?['Year'] ?? entry['ObsKey']?['TIME_PERIOD']; - final formattedTimePeriod = rawTimePeriod != null - ? rawTimePeriod.toString() // Use as-is if valid - : 'Unknown'; // Fallback value if null + // Group by timePeriod (year) and sum the values + Map groupedData = {}; - // Parse the value or fallback to 0.0 + response.forEach((entry) { + final year = entry['ObsKey']['TIME_PERIOD']?.toString() ?? 'Unknown'; final value = - double.tryParse(entry['ObsValue']?['Value']?.toString() ?? '0.0') ?? + double.tryParse(entry['ObsValue']['Value']?.toString() ?? '0.0') ?? 0.0; - return ChartData( - timePeriod: formattedTimePeriod, - value: value, - ); - }).toList(); + // Add the value to the existing year (if exists) or initialize it + if (groupedData.containsKey(year)) { + groupedData[year] = groupedData[year]! + value; + } else { + groupedData[year] = value; + } + }); + + // Sort the grouped data by year in ascending order + var sortedEntries = groupedData.entries.toList() + ..sort((a, b) => int.parse(a.key).compareTo(int.parse(b.key))); + + // Convert the sorted data to a list of ChartData + return sortedEntries + .map((entry) => ChartData( + timePeriod: entry.key, + value: entry.value, + value1: 0.0)) // Add value1 as 0.0 + .toList(); } List parseBarChartData(List response) { @@ -82,63 +95,127 @@ class ChartPageState extends State { .asMap() .entries .map((entry) => ChartData( - timePeriod: entry.value['ObsKey']['TIME_PERIOD'] ?? - 'Unknown', // Fallback to 'Unknown' if null + timePeriod: entry.value['ObsKey']['TIME_PERIOD'] ?? 'Unknown', value: double.tryParse( entry.value['ObsValue']['Value'].toString()) ?? - 0.0, // Handle null/invalid value + 0.0, + value1: 0.0, // Ensure value1 is passed as well )) .toList(); } + List parseColumnChartData(List response) { + List chartDataList = []; + + // Temporary map to store values by TIME_PERIOD + Map timePeriodMap = {}; + + for (var entry in response) { + String timePeriod = entry['ObsKey']['TIME_PERIOD'].toString(); + double value = + double.tryParse(entry['ObsValue']['Value']?.toString() ?? '0.0') ?? + 0.0; + String indicator = entry['ObsKey']['H_INDICATOR']; + + // If the entry is for TOR, map it to 'value' + if (indicator == 'TOR') { + if (timePeriodMap.containsKey(timePeriod)) { + // Update the value by creating a new ChartData object + timePeriodMap[timePeriod] = ChartData( + timePeriod: timePeriod, + value: value, // Set the new value + value1: timePeriodMap[timePeriod]?.value1 ?? 0.0, + ); + } else { + timePeriodMap[timePeriod] = ChartData( + timePeriod: timePeriod, + value: value, + value1: 0.0, + ); + } + } + + // If the entry is for TAR, map it to 'value1' + if (indicator == 'TAR') { + if (timePeriodMap.containsKey(timePeriod)) { + // Create a new ChartData object with the updated value1 + timePeriodMap[timePeriod] = ChartData( + timePeriod: timePeriod, + value: timePeriodMap[timePeriod]?.value ?? + 0.0, // Retain the current value + value1: value, // Update the value1 + ); + } else { + timePeriodMap[timePeriod] = ChartData( + timePeriod: timePeriod, + value: 0.0, + value1: value, + ); + } + } + } + + // Convert the map to a list + chartDataList = timePeriodMap.values.toList(); + + return chartDataList; + } + Widget buildChart(dynamic chartData) { print('chartData $chartData'); switch (chartData['chart_type']) { - case 'line': + case 'area': return SfCartesianChart( - primaryXAxis: CategoryAxis( - title: AxisTitle(text: 'Year'), - labelRotation: 45, // Rotate labels if they overlap - ), - title: ChartTitle(text: 'Hotel Estabhlishments'), - legend: Legend(isVisible: true), - tooltipBehavior: TooltipBehavior( - enable: true, - builder: (dynamic data, dynamic point, dynamic series, - int pointIndex, int seriesIndex) { - return Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(5), - ), - child: Text( - '${data.timePeriod}: ${formatNumber(data.value)}', - style: const TextStyle(color: Colors.black), - ), - ); - }, - ), - series: >[ - LineSeries( - dataSource: parseLineChartData(chartData['response']), - xValueMapper: (ChartData data, _) => data.timePeriod, - yValueMapper: (ChartData data, _) => data.value, - name: chartData['dataset'], - color: Colors.red, - dataLabelSettings: DataLabelSettings( - isVisible: true, - builder: (dynamic data, dynamic point, dynamic series, - int pointIndex, int seriesIndex) { - return Text( - formatNumber(data.value), - style: const TextStyle(fontSize: 12, color: Colors.black), - ); - }, - ), + primaryXAxis: CategoryAxis( + title: AxisTitle(text: 'Year'), + labelRotation: 45, // Rotate labels if they overlap ), - ], - ); + title: ChartTitle(text: 'Hotel Estabhlishments'), + legend: Legend(isVisible: true), + tooltipBehavior: TooltipBehavior( + enable: true, + builder: (dynamic data, dynamic point, dynamic series, + int pointIndex, int seriesIndex) { + return Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + ), + child: Text( + '${data.timePeriod}: ${formatNumber(data.value)}', + style: const TextStyle(color: Colors.black), + ), + ); + }, + ), + series: [ + // AreaSeries for the area chart + AreaSeries( + dataSource: parseLineChartData( + chartData['response']), // your chart data + color: Color(0xFF7DAFBC), // Area color + borderDrawMode: BorderDrawMode + .excludeBottom, // Optional, adjust border settings + borderColor: Colors.green, // Border color + borderWidth: 2, // Border width + xValueMapper: (ChartData data, _) => + data.timePeriod, // Map x to DateTime + yValueMapper: (ChartData data, _) => data.value, + // name: chartData['dataset'], + name: 'Hotels', + dataLabelSettings: DataLabelSettings( + isVisible: true, + builder: (dynamic data, dynamic point, dynamic series, + int pointIndex, int seriesIndex) { + return Text( + formatNumber(data.value), + style: const TextStyle(fontSize: 12, color: Colors.black), + ); + }, + ), // Map y to numerical value + ) + ]); case 'bar': return SfCartesianChart( primaryXAxis: CategoryAxis(), @@ -166,7 +243,7 @@ class ChartPageState extends State { xValueMapper: (ChartData data, _) => data.timePeriod, yValueMapper: (ChartData data, _) => data.value, name: chartData['dataset'], - color: Colors.red, + color: Color(0xFF7DAFBC), dataLabelSettings: DataLabelSettings( isVisible: true, builder: (dynamic data, dynamic point, dynamic series, @@ -182,6 +259,41 @@ class ChartPageState extends State { ), ], ); + case 'column': + return SfCartesianChart( + primaryXAxis: CategoryAxis( + title: AxisTitle(text: 'Year'), + labelRotation: 45, // Rotate labels if they overlap + ), + primaryYAxis: NumericAxis( + title: AxisTitle(text: 'No of rooms'), + ), + legend: Legend(isVisible: true), + title: ChartTitle(text: 'Hotel Occupancy Rate'), + enableSideBySideSeriesPlacement: true, + tooltipBehavior: TooltipBehavior(enable: true), + series: >[ + // First Series (value) + + ColumnSeries( + name: 'Available Rooms', + // opacity: 0.9, + // width: 0.4, + color: Color(0xFF587BA3), + dataSource: parseColumnChartData(chartData['response']), + xValueMapper: (ChartData data, _) => int.parse(data.timePeriod), + yValueMapper: (ChartData data, _) => data.value1, + ), + // Second Series (value1) + ColumnSeries( + color: Color(0xFF90B0D5), + name: 'Occupied Rooms', + dataSource: parseColumnChartData(chartData['response']), + xValueMapper: (ChartData data, _) => int.parse(data.timePeriod), + yValueMapper: (ChartData data, _) => data.value, + ), + ], // Numeric Y-axis to plot the values + ); default: return Center(child: Text('Unknown chart type')); } @@ -192,46 +304,84 @@ class ChartPageState extends State { return Scaffold( backgroundColor: const Color(0xFF7DAFBC), appBar: AppBar( - backgroundColor: Color(0xFF7DAFBC), + backgroundColor: const Color(0xFF7DAFBC), elevation: 0, leading: IconButton( - icon: Icon(Icons.arrow_back_ios_new, color: Colors.white), + icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white), onPressed: () { context.go('/myhomepage'); }, ), - title: Text( + title: const Text( 'Charts', style: TextStyle(color: Colors.white), ), ), body: isLoading - ? Center(child: CircularProgressIndicator()) - : ListView.builder( - itemCount: chartsData.length, - itemBuilder: (context, index) { - return Card( - margin: EdgeInsets.all(10), + ? const Center(child: CircularProgressIndicator()) + : Column( + children: [ + Card( + margin: const EdgeInsets.all(10), + color: Colors.white, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Text( - // 'Chart: ${chartsData[index]['chart_type']}', - // style: TextStyle( - // fontSize: 18, fontWeight: FontWeight.bold), - // ), - // SizedBox(height: 10), - Container( - height: 300, - child: buildChart(chartsData[index]), + children: const [ + Text( + 'Card 1 Title', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), ), + SizedBox(height: 10), + Text('Content for the first card goes here.'), ], ), ), - ); - }, + ), + Card( + margin: const EdgeInsets.all(10), + color: Colors.white, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text( + 'Card 2 Title', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), + ), + SizedBox(height: 10), + Text('Content for the second card goes here.'), + ], + ), + ), + ), + Expanded( + child: ListView.builder( + itemCount: chartsData.length, + itemBuilder: (context, index) { + return Card( + margin: const EdgeInsets.all(10), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 300, + child: buildChart(chartsData[index]), + ), + ], + ), + ), + ); + }, + ), + ), + ], ), ); } @@ -240,23 +390,35 @@ class ChartPageState extends State { class ChartData { final String timePeriod; final double value; + final double value1; final String formattedValue; - ChartData({required this.timePeriod, required this.value}) + ChartData( + {required this.timePeriod, required this.value, required this.value1}) : formattedValue = formatNumber(value); } +// String formatNumber(double value) { +// if (value >= 1e12) { +// return '${(value / 1e12).toStringAsFixed(2)} T'; // Trillions +// } else if (value >= 1e9) { +// return '${(value / 1e9).toStringAsFixed(2)} B'; // Billions +// } else if (value >= 1e6) { +// return '${(value / 1e6).toStringAsFixed(2)} M'; // Millions +// } else if (value >= 1e5) { +// return '${(value / 1e5).toStringAsFixed(2)} L'; // Lakhs +// } else if (value >= 1e3) { +// return '${(value / 1e3).toStringAsFixed(2)} K'; // Thousands +// } +// return value.toStringAsFixed(2); // Default to two decimal places +// } + String formatNumber(double value) { - if (value >= 1e12) { - return '${(value / 1e12).toStringAsFixed(2)} T'; // Trillions - } else if (value >= 1e9) { + if (value >= 1e9) { return '${(value / 1e9).toStringAsFixed(2)} B'; // Billions } else if (value >= 1e6) { return '${(value / 1e6).toStringAsFixed(2)} M'; // Millions - } else if (value >= 1e5) { - return '${(value / 1e5).toStringAsFixed(2)} L'; // Lakhs - } else if (value >= 1e3) { - return '${(value / 1e3).toStringAsFixed(2)} K'; // Thousands } - return value.toStringAsFixed(2); // Default to two decimal places + return value + .toStringAsFixed(2); // Default to two decimal places for smaller numbers } diff --git a/lib/presentation/Screens/demo_home2.dart b/lib/presentation/Screens/demo_home2.dart new file mode 100644 index 00000000..b1576aaa --- /dev/null +++ b/lib/presentation/Screens/demo_home2.dart @@ -0,0 +1,328 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:intl/intl.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; // Import Syncfusion charts package +import 'package:http/http.dart' as http; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: ChartPage2(dataSets: ''), + ); + } +} + +class ChartPage2 extends StatefulWidget { + final String dataSets; + const ChartPage2({Key? key, required this.dataSets}); + @override + ChartPageState createState() => ChartPageState(); +} + +class ChartPageState extends State { + List chartsData = []; + bool isLoading = true; + + @override + void initState() { + super.initState(); + fetchChartData(widget.dataSets); + } + + Future fetchChartData(dataSets) async { + const baseUrl = + 'https://pb.venbait.in/api/custom/apicall'; // Replace with your server URL + final url = Uri.parse('$baseUrl?dataset=$dataSets'); + + try { + final response = await http.get(url); + if (response.statusCode == 200) { + setState(() { + chartsData = jsonDecode(response.body); + isLoading = false; + }); + } else { + throw Exception('Failed to load data'); + } + } catch (error) { + setState(() { + isLoading = false; + }); + print('Error fetching data: $error'); + } + } + + List parseLineChartData(List response) { + return response.map((entry) { + // Attempt to fetch the TIMEPERIOD + final rawTimePeriod = + entry['ObsKey']?['Year'] ?? entry['ObsKey']?['TIME_PERIOD']; + final formattedTimePeriod = rawTimePeriod != null + ? rawTimePeriod.toString() // Use as-is if valid + : 'Unknown'; // Fallback value if null + + // Parse the value or fallback to 0.0 + final value = + double.tryParse(entry['ObsValue']?['Value']?.toString() ?? '0.0') ?? + 0.0; + + return ChartData( + timePeriod: formattedTimePeriod, + value: value, + ); + }).toList(); + } + + List parseBarChartData(List response) { + return response + .asMap() + .entries + .map((entry) => ChartData( + timePeriod: entry.value['ObsKey']['TIME_PERIOD'] ?? + 'Unknown', // Fallback to 'Unknown' if null + value: double.tryParse( + entry.value['ObsValue']['Value'].toString()) ?? + 0.0, // Handle null/invalid value + )) + .toList(); + } + +// Update parseColumnChartData for column chart + List parseColumnChartData(List response) { + return response.map((entry) { + // Extract TIME_PERIOD as x + final x = + int.tryParse(entry['ObsKey']?['TIME_PERIOD']?.toString() ?? '0') ?? 0; + // Extract Value as y + final y = + double.tryParse(entry['ObsValue']?['Value']?.toString() ?? '0.0') ?? + 0.0; + return ChartData(xInt: x, y: y); + }).toList(); + } + +// Update parseScatterChartData for scatter chart + List parseScatterChartData(List response) { + return response.map((entry) { + // Parse TIME_PERIOD into DateTime + final timePeriod = entry['ObsKey']?['TIME_PERIOD']; + final x = timePeriod != null + ? DateTime.tryParse('$timePeriod-01-01') // Convert year to DateTime + : DateTime.now(); // Fallback to current date if null + + // Parse Value into double + final y = + double.tryParse(entry['ObsValue']?['Value']?.toString() ?? '0.0') ?? + 0.0; + + return ChartData(xDateTime: x, y: y); + }).toList(); + } + + Widget buildChart(dynamic chartData) { + switch (chartData['chart_type']) { + case 'scatter': + return SfCartesianChart( + primaryXAxis: DateTimeAxis(), // Ensure it's DateTimeAxis + tooltipBehavior: TooltipBehavior(enable: true), + title: ChartTitle(text: 'Divorces'), + series: [ + ScatterSeries( + dataSource: parseScatterChartData(chartData['response']), + xValueMapper: (ChartData data, _) => + data.xDateTime ?? DateTime.now(), + yValueMapper: (ChartData data, _) => data.y, + name: chartData['dataset'], + dataLabelSettings: DataLabelSettings(isVisible: true), + ), + ], + ); + case 'column': + return SfCartesianChart( + primaryXAxis: NumericAxis( + title: AxisTitle(text: 'Year'), + interval: 1, // Ensure no fractional intervals + ), + title: ChartTitle(text: 'Marriages'), + series: >[ + ColumnSeries( + dataSource: parseColumnChartData(chartData['response']), + xValueMapper: (ChartData data, _) => data.xInt ?? 0, + yValueMapper: (ChartData data, _) => data.y, + name: chartData['dataset'], + color: Color(0xFF7DAFBC), + width: 0.8, + spacing: 0.2, + dataLabelSettings: + DataLabelSettings(isVisible: true), // Show data labels + ), + ], + ); + + case 'line': + return SfCartesianChart( + primaryXAxis: CategoryAxis(), + title: ChartTitle(text: 'Line Chart'), + legend: Legend(isVisible: true), + tooltipBehavior: TooltipBehavior(enable: true), + series: >[ + LineSeries( + dataSource: parseLineChartData(chartData['response']), + xValueMapper: (ChartData data, _) => data.timePeriod ?? '', + yValueMapper: (ChartData data, _) => data.value ?? 0.0, + name: 'Sales', + dataLabelSettings: DataLabelSettings(isVisible: true), + ), + ], + ); + case 'bar': + return SfCartesianChart( + primaryXAxis: CategoryAxis(), + title: ChartTitle(text: 'Bar Chart'), + tooltipBehavior: TooltipBehavior(enable: true), + series: >[ + BarSeries( + dataSource: parseBarChartData(chartData['response']), + xValueMapper: (ChartData data, _) => data.timePeriod ?? '', + yValueMapper: (ChartData data, _) => data.value ?? 0.0, + name: 'Gold', + color: Color.fromRGBO(8, 142, 255, 1), + ), + ], + ); + + default: + return Center(child: Text('Unknown chart type')); + } + } + + @override + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFF7DAFBC), + appBar: AppBar( + backgroundColor: Color(0xFF7DAFBC), + elevation: 0, + leading: IconButton( + icon: Icon(Icons.arrow_back_ios_new, color: Colors.white), + onPressed: () { + context.go('/myhomepage'); + }, + ), + title: Text( + 'Charts', + style: TextStyle(color: Colors.white), + ), + ), + body: isLoading + ? Center(child: CircularProgressIndicator()) + : Column( + children: [ + // Row for two cards + Row( + children: [ + // First Card + Expanded( + flex: 6, + child: Card( + margin: EdgeInsets.all(10), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Card 1', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), + ), + SizedBox(height: 10), + Text('Content for Card 1 goes here.'), + ], + ), + ), + ), + ), + // Second Card + Expanded( + flex: 6, + child: Card( + margin: EdgeInsets.all(10), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Card 2', + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), + ), + SizedBox(height: 10), + Text('Content for Card 2 goes here.'), + ], + ), + ), + ), + ), + ], + ), + // List of charts + Expanded( + child: ListView.builder( + itemCount: chartsData.length, + itemBuilder: (context, index) { + return Card( + margin: EdgeInsets.all(10), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 10), + Container( + height: 300, + child: buildChart(chartsData[index]), + ), + ], + ), + ), + ); + }, + ), + ), + ], + ), + ); + } +} + +class ChartData { + final int? xInt; // For column and other charts, using int for x + final DateTime? xDateTime; // For scatter charts, using DateTime for x + final double y; // For column and other charts + final String? timePeriod; // For line and bar charts + final double? value; // For line and bar charts + final double? y2; // For secondary data (stacked column chart) + final double? y3; // For tertiary data (stacked column chart) + final double? y4; // For quaternary data (stacked column chart) + + // Constructor that handles all chart cases + ChartData({ + this.xInt, + this.xDateTime, + this.y = 0.0, + this.timePeriod, + this.value, + this.y2, + this.y3, + this.y4, + }); +} diff --git a/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart b/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart index cc4702c4..70ed8841 100644 --- a/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart +++ b/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart @@ -171,6 +171,20 @@ class MyHomePage extends StatefulWidget { State createState() => _MyHomePageState(); } +void handleInfoCardClick(BuildContext context, String data) { + // Handle navigation and pass dynamic data + print(data); + final dataSets = data; + print(dataSets); + if (dataSets == 'hotels') { + context.go('/Chart/$dataSets'); + } else if (dataSets == 'divorces') { + context.go('/Chart/$dataSets'); + } else if (dataSets == 'marriages') { + context.go('/Chart/$dataSets'); + } +} + class _MyHomePageState extends State { @override Widget build(BuildContext context) { @@ -226,6 +240,7 @@ class EconomyStats extends StatelessWidget { value: '1.62T', bordercolor: Color(0xFF7DAFBC), textcolor: Color(0xFF7DAFBC), + onTap: () => handleInfoCardClick(context, 'Inflation'), ), InfoCard( title: 'Inflation Rate', @@ -233,6 +248,7 @@ class EconomyStats extends StatelessWidget { value: '4.82%', bordercolor: Color(0xFF7DAFBC), textcolor: Color(0xFF7DAFBC), + onTap: () => handleInfoCardClick(context, 'Inflation'), ), ], ), @@ -247,6 +263,8 @@ class EconomyStats extends StatelessWidget { value: '215.4B', bordercolor: Color(0xFF7DAFBC), textcolor: Color(0xFF7DAFBC), + onTap: () => + handleInfoCardClick(context, 'commodities'), ), InfoCard( title: 'Hotel Guests', @@ -254,6 +272,7 @@ class EconomyStats extends StatelessWidget { value: '25.21M', bordercolor: Color(0xFF7DAFBC), textcolor: Color(0xFF7DAFBC), + onTap: () => handleInfoCardClick(context, 'hotels'), ), ], ), @@ -280,18 +299,22 @@ class EconomyStats extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ InfoCard( - title: 'Population', - subtitle: '(2022) (AED)', - value: '1.62T', + title: 'Divorces', + subtitle: '(2022)', + value: '0', bordercolor: Colors.brown[200]!, textcolor: Colors.brown[200]!, + onTap: () => + handleInfoCardClick(context, 'divorces'), ), InfoCard( - title: 'Labor Force', + title: 'Marriages', subtitle: '(2022)', - value: '4.82%', + value: '0', bordercolor: Colors.brown[200]!, textcolor: Colors.brown[200]!, + onTap: () => + handleInfoCardClick(context, 'marriages'), ), ], ), @@ -301,11 +324,13 @@ class EconomyStats extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ InfoCard( - title: 'Hospitals (Gov )', + title: 'Hospitals (Gov)', subtitle: '(Jan - Jan 2024) - AED', value: '215.4B', bordercolor: Colors.brown[200]!, textcolor: Colors.brown[200]!, + onTap: () => + handleInfoCardClick(context, 'Hospitals'), ), InfoCard( title: 'Schools', @@ -313,6 +338,8 @@ class EconomyStats extends StatelessWidget { value: '25.21M', bordercolor: Colors.brown[200]!, textcolor: Colors.brown[200]!, + onTap: () => + handleInfoCardClick(context, 'Schools'), ), ], ), @@ -345,6 +372,8 @@ class EconomyStats extends StatelessWidget { value: '1.62T', bordercolor: Color(0xFF7DAFBC), textcolor: Color(0xFF7DAFBC), + onTap: () => handleInfoCardClick( + context, 'Electricity'), ), InfoCard( title: 'Cruid oil', @@ -352,6 +381,8 @@ class EconomyStats extends StatelessWidget { value: '4.82%', bordercolor: Color(0xFF7DAFBC), textcolor: Color(0xFF7DAFBC), + onTap: () => + handleInfoCardClick(context, 'Cruid'), ), ], ), @@ -367,6 +398,8 @@ class EconomyStats extends StatelessWidget { value: '215.4B', bordercolor: Color(0xFF7DAFBC), textcolor: Color(0xFF7DAFBC), + onTap: () => + handleInfoCardClick(context, 'Export'), ), InfoCard( title: 'Desalinated water', @@ -374,6 +407,8 @@ class EconomyStats extends StatelessWidget { value: '25.21M', bordercolor: Color(0xFF7DAFBC), textcolor: Color(0xFF7DAFBC), + onTap: () => handleInfoCardClick( + context, 'Desalinated'), ), ], ), @@ -433,6 +468,7 @@ class InfoCard extends StatelessWidget { final String value; final Color bordercolor; final Color? textcolor; + final VoidCallback onTap; // New callback parameter const InfoCard({ Key? key, @@ -441,6 +477,7 @@ class InfoCard extends StatelessWidget { required this.value, required this.bordercolor, this.textcolor, + required this.onTap, // Initialize in constructor }) : super(key: key); @override @@ -450,9 +487,7 @@ class InfoCard extends StatelessWidget { return Expanded( child: GestureDetector( - onTap: () { - context.go('/DemoHome'); - }, + onTap: onTap, // Use callback for handling clicks child: Container( height: myheight / 9, width: mywidth / 4, diff --git a/pubspec.yaml b/pubspec.yaml index ebd8679b..315a6f95 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: injectable: ^2.3.2 riverpod_annotation: ^2.3.3 cupertino_icons: ^1.0.6 - fl_chart: ^0.69.0 + fl_chart: ^0.69.2 marquee: ^2.2.3 # dynamic_layouts: # git: From fb68b1aa7969729ecfdb43358adcf15baf8ffce0 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Tue, 7 Jan 2025 10:56:50 +0530 Subject: [PATCH 2/3] drawer profile functionality added --- lib/config/my_router.dart | 2 +- .../drawer_routes/custom_drawer_routes.dart | 213 ++++++++++++++---- pubspec.lock | 48 ++-- 3 files changed, 192 insertions(+), 71 deletions(-) diff --git a/lib/config/my_router.dart b/lib/config/my_router.dart index e45df2d3..c200ac19 100644 --- a/lib/config/my_router.dart +++ b/lib/config/my_router.dart @@ -324,7 +324,7 @@ final GoRouter router = GoRouter( GoRoute( path: '/', builder: (context, state) => LoginRoute(), - //builder: (context, state) => MyHomePage(), + // builder: (context, state) => MyHomePage(), ), GoRoute( path: '/login', diff --git a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart index de8715ed..cad24495 100644 --- a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart +++ b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:pocketbase/pocketbase.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:shared_preferences/shared_preferences.dart'; -class BaseScaffold extends StatelessWidget { +class BaseScaffold extends StatefulWidget { final Widget body; final Widget title; final List? actions; @@ -12,9 +15,81 @@ class BaseScaffold extends StatelessWidget { required this.title, this.actions, }) : super(key: key); - // final Widget body; - // - // const BaseScaffold({required this.body}); + + @override + _BaseScaffoldState createState() => _BaseScaffoldState(); +} + +class _BaseScaffoldState extends State { + final _pb = PocketBase('https://pb.venbait.in'); + String _avatarUrl = ''; + dynamic userId; + String? userName; + String? userEmail; + String? userAvatar; + String? role; + + @override + void initState() { + super.initState(); + _checkUserId(); + } + + // Method to retrieve userId from SharedPreferences + Future getUserId() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('userId'); // Retrieve the userId + } + + // Method to check if userId exists and update state + Future _checkUserId() async { + String? fetchedUserId = await getUserId(); + if (fetchedUserId != null && fetchedUserId.isNotEmpty) { + setState(() { + userId = fetchedUserId; + }); + //print('NAVUser ID: $userId'); + _fetchUserData(); + } else { + print('No userId found'); + // Handle the case where userId is not available + } + } + + Future _fetchUserData() async { + try { + final adminAuth = await _pb.admins + .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + final adminToken = adminAuth.token; + //print('adminToken- ${adminToken}'); + final userDetailsResponse = await _pb.collection('users').getOne( + userId!, + headers: { + 'Authorization': 'Bearer $adminToken', + }, + ); + print('NAVuserDetails: $userDetailsResponse'); + + setState(() { + userName = userDetailsResponse.data['username'] ?? ''; + userEmail = userDetailsResponse.data['email'] ?? ''; + userAvatar = userDetailsResponse.data['avatar'] ?? ''; + role = userDetailsResponse.data['role'] ?? ''; + String recordId = userId; + String collectionId = + userDetailsResponse.data['collectionId'] ?? '_pb_users_auth_'; + + if (userAvatar!.isNotEmpty && recordId.isNotEmpty) { + _avatarUrl = + 'https://pb.venbait.in/api/files/$collectionId/$recordId/$userAvatar'; + } else { + _avatarUrl = ''; // Reset to default or empty + } + }); + } catch (e) { + print('Error fetching user details: $e'); + } + } @override Widget build(BuildContext context) { @@ -22,15 +97,29 @@ class BaseScaffold extends StatelessWidget { String currentRoute = GoRouterState.of(context).matchedLocation; double myheight = MediaQuery.of(context).size.height; double mywidth = MediaQuery.of(context).size.width; + return Scaffold( - appBar: AppBar(title: title, actions: [ - IconButton(onPressed: () {}, icon: Icon(Icons.toggle_off_outlined)), - ]), + appBar: AppBar( + title: widget.title, + actions: [ + // IconButton( + // onPressed: () { + // // Share logic here + // print("Share icon pressed"); + // Share.share( + // 'Open the app: fcscapp://home\n\n' + // 'If you don’t have the app installed, ' + // 'visit: http://localhost:65493'); + // }, + // icon: Icon(Icons.share), + // ), + IconButton(onPressed: () {}, icon: Icon(Icons.toggle_off_outlined)), + ], + ), drawer: Drawer( child: ListView( children: [ DrawerHeader( - //decoration: BoxDecoration(color: Colors.blue), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -48,18 +137,39 @@ class BaseScaffold extends StatelessWidget { child: Row( children: [ SizedBox( - width: mywidth / 8, - child: Image( - image: AssetImage( - 'assets/edit_profile/profile.png'))), + 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('Mohammad'), - Text('Mohammad@fcsc.com') + // Text(userId ?? 'Loading user...'), // Display userId here + // Text('Mohammad@fcsc.com') + Text(userName ?? 'Loading...'), + Text( + userEmail ?? 'Loading...', + style: TextStyle(fontSize: 12), + ), ], ) ], @@ -68,34 +178,45 @@ class BaseScaffold extends StatelessWidget { ], ), ), + if (role != 'admin') + ListTile( + leading: SizedBox( + height: myheight / 15, + width: mywidth / 15, + child: Image( + image: AssetImage('assets/icons/drawer/message.png'))), + title: Text('Feedback'), + onTap: () => context.go('/feedback'), + ), + if (role == 'admin') + ListTile( + leading: SizedBox( + height: myheight / 15, + width: mywidth / 15, + child: Image( + image: + AssetImage('assets/icons/drawer/manageuser.png'))), + title: Text('Manage User'), + onTap: () => context.go('/manageuser'), + ), ListTile( leading: SizedBox( height: myheight / 15, width: mywidth / 15, - child: Image( - image: AssetImage('assets/icons/drawer/message.png'))), - title: Text('Feedback'), - onTap: () => context.go('/feedback'), - ), - ListTile( - leading: SizedBox( - height: myheight / 15, - width: mywidth / 15, - child: Image( - image: AssetImage('assets/icons/drawer/manageuser.png'))), - title: Text('Manage User'), - onTap: () => context.go('/manageuser'), - ), - - ListTile( - leading: SizedBox( - height: myheight / 15, - width: mywidth / 15, - child: Image( - image: AssetImage('assets/icons/drawer/book.png'))), + child: + Image(image: AssetImage('assets/icons/drawer/book.png'))), title: Text('User Guide'), onTap: () => context.go('/user-guide'), ), + ListTile( + leading: SizedBox( + height: myheight / 15, + width: mywidth / 15, + child: Image( + image: AssetImage('assets/icons/drawer/logout.png'))), + title: Text('Logout'), + onTap: () => context.go('/'), + ), ], ), ), @@ -140,40 +261,40 @@ class BaseScaffold extends StatelessWidget { unselectedItemColor: Colors.grey, showUnselectedLabels: true, ), - body: body, + body: widget.body, ); } - //Map the current route to the selected index + // Map the current route to the selected index int _getSelectedIndex(String route) { switch (route) { - case '/': + case '/myhomepage': return 0; - case '/uaenumbers': + case '/about': return 1; - case '/competitiveness': + case '/settings': return 2; - case '/countryprofile': + case '/profile': return 3; default: return 0; } } - //Handle navigation when an item is tapped + // Handle navigation when an item is tapped void _onItemTapped(BuildContext context, int index) { switch (index) { case 0: - context.go('/'); + context.go('/myhomepage'); break; case 1: - context.go('/uaenumbers'); + context.go('/about'); break; case 2: - context.go('/competitiveness'); + context.go('/settings'); break; case 3: - context.go('/countryprofile'); + context.go('/profile'); break; } } diff --git a/pubspec.lock b/pubspec.lock index cc69ece0..3996cee4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "76.0.0" + version: "72.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.3" + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "6.7.0" analyzer_plugin: dependency: transitive description: @@ -186,10 +186,10 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.18.0" convert: dependency: transitive description: @@ -266,10 +266,10 @@ packages: dependency: transitive description: name: custom_lint_visitor - sha256: bfe9b7a09c4775a587b58d10ebb871d4fe618237639b1e84d5ec62d7dfef25f9 + sha256: "8aeb3b6ae2bb765e7716b93d1d10e8356d04e0ff6d7592de6ee04e0dd7d6587d" url: "https://pub.dev" source: hosted - version: "1.0.0+6.11.0" + version: "1.0.0+6.7.0" dart_style: dependency: transitive description: @@ -773,18 +773,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -813,10 +813,10 @@ packages: dependency: transitive description: name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" url: "https://pub.dev" source: hosted - version: "0.1.3-main.0" + version: "0.1.2-main.4" mailer: dependency: "direct main" description: @@ -1172,7 +1172,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.0" + version: "0.0.99" smart_api_client: dependency: "direct main" description: @@ -1216,10 +1216,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.11.1" state_notifier: dependency: transitive description: @@ -1248,10 +1248,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.2.0" syncfusion_flutter_charts: dependency: "direct main" description: @@ -1280,10 +1280,10 @@ packages: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.2" the_validator: dependency: "direct main" description: @@ -1408,10 +1408,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.2.5" watcher: dependency: transitive description: From b326901d4bd437f4f7e5887e160b175fe3ede996 Mon Sep 17 00:00:00 2001 From: Kalonkarthik Date: Tue, 7 Jan 2025 18:02:54 +0530 Subject: [PATCH 3/3] User Guide v_7 Update --- lib/config/my_router.dart | 17 +- .../Drawer Items/user_guide/custom_text.dart | 94 +- .../Drawer Items/user_guide/faq_page.dart | 294 ++++- .../Drawer Items/user_guide/features.dart | 1151 ++++++++--------- .../user_guide/getting_started.dart | 277 ++-- .../Drawer Items/user_guide/user_guide.dart | 8 +- .../drawer_routes/custom_drawer_routes.dart | 32 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 80 +- pubspec.yaml | 1 + 10 files changed, 1080 insertions(+), 876 deletions(-) diff --git a/lib/config/my_router.dart b/lib/config/my_router.dart index 062e04af..263b4a1f 100644 --- a/lib/config/my_router.dart +++ b/lib/config/my_router.dart @@ -383,16 +383,15 @@ final GoRouter router = GoRouter( builder: (context, state) => UsingFeatures(), ), -GoRoute(path: '/faq', -name: 'faq', -builder: (context, state) => FAQPage(), -), + GoRoute(path: '/faq', + name: 'faq', + builder: (context, state) => FAQPage(), + ), - -GoRoute(path: '/gettingStarted', -name: 'gettingStarted', -builder: (context, state) => GettingStarted(), -), + GoRoute(path: '/gettingStarted', + name: 'gettingStarted', + builder: (context, state) => GettingStarted(), + ), ], ), // GoRoute( diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/custom_text.dart b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/custom_text.dart index 5abc4efd..a20db95f 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/custom_text.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/custom_text.dart @@ -1,64 +1,56 @@ import 'package:flutter/material.dart'; -class CustomContainer extends StatelessWidget { - final String text; - final EdgeInsetsGeometry padding; - final bool isBulletPoint; - final TextStyle? textStyle; - final double? fontSize; - final double? height; - final double defaultFontSize ; - final TextDecoration textDecoration ; - final Color? decorationColor; - final FontWeight fontWeight; - final Color color; +class CustomTextRich extends StatelessWidget { - const CustomContainer({ + const CustomTextRich({ super.key, - required this.text, + required this.textSpans, this.padding = const EdgeInsets.all(8.0), - this.isBulletPoint = false, - this.textStyle, - this.fontSize, - this.defaultFontSize=14, - this.height, - this.textDecoration = TextDecoration.none, - this.fontWeight =FontWeight.w500, - this.decorationColor, - this.color=const Color(0xFF898C81), + this.textAlign = TextAlign.left, }); - + final List textSpans; + final EdgeInsetsGeometry padding; + final TextAlign textAlign; @override Widget build(BuildContext context) { - return Padding( - padding: padding, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (isBulletPoint) - Text( - "• ", + return SingleChildScrollView( + child :Padding( + padding: padding, + child: Text.rich( + TextSpan(children: textSpans), + textAlign: textAlign, + ), + ), + ); - style: TextStyle(fontSize: fontSize ?? defaultFontSize, - color: color, - fontWeight: fontWeight, - height: height,), - ), - Expanded( - child: Text( - text, - style: TextStyle( - fontSize: fontSize ?? defaultFontSize, - color: color, - fontWeight: fontWeight, - height: height, - decoration: textDecoration, - decorationColor: decorationColor ?? color, - ), - ), - ), - ], + + } + + static TextSpan createTextSpan( + String text, { + double? fontSize, + Color color = const Color(0xFF898C81), + FontWeight fontWeight = FontWeight.w500, + double? height, + double defaultFontSize = 14, + TextDecoration textDecoration = TextDecoration.none, + Color? decorationColor, + double spacingTop = 0.0, + double spacingBottom = 0.0, + }) { + String spacedText = (spacingTop > 0.0 || spacingBottom > 0.0) + ? '${'\n' * spacingTop.toInt()}$text${'\n' * spacingBottom.toInt()}' + : text; + return TextSpan( + text: spacedText, + style: TextStyle( + fontSize: fontSize ?? defaultFontSize, + color: color, + fontWeight: fontWeight, + height: height, + decoration: textDecoration, + decorationColor: decorationColor ?? color, ), ); } diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/faq_page.dart b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/faq_page.dart index ad39293c..bf2b4771 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/faq_page.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/faq_page.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import '../../custom_drawer_routes.dart'; +import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; class FAQPage extends StatefulWidget { const FAQPage({super.key}); @@ -13,38 +13,63 @@ class _FAQPageState extends State { @override Widget build(BuildContext context) { return BaseScaffold( - title: Text('FAQs'), - body: Scrollbar( - thumbVisibility: true, - thickness: 6, - radius: Radius.circular(10), - interactive: true, - child:SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + title: Text('FAQs', + style: TextStyle( + color:Color(0xFF7296BE), + fontWeight: FontWeight.w500, + ),), + showBackIcon: true, + body: SafeArea(child:Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.all(16.0), + width: MediaQuery.of(context).size.width, + child: Text.rich( + TextSpan( + text: 'Here are some common questions about the ', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w600, + color: Colors.grey, + ), children: [ - Container( - padding: const EdgeInsets.all(16.0), - width: MediaQuery.of(context).size.width, - child: Text( - 'HERE ARE SOME COMMON QUESTIONS ABOUT THE FEDERAL COMPETITIVENESS AND STATISTICS CENTRE (FCSC) MOBILE APPLICATION:', - style: TextStyle( - color: Color(0xFF265E84), // Text color - fontSize: 18, - fontWeight: FontWeight.w600 - ), + TextSpan( + text:' Federal Competitiveness and Statistics Centre (FCSC),', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + TextSpan( + text: 'mobile application:', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w600, + color: Colors.grey, ), ), - Container( - padding: const EdgeInsets.all(0.0), - color: Colors.grey, - child: QuestionAnswerScrollView() - ) ], ), ), ), - + Expanded(child: Scrollbar( + thumbVisibility: true, + thickness: 8, + radius: Radius.circular(10), + interactive: true, + child:SingleChildScrollView( + child:Container( + padding: const EdgeInsets.all(0.0), + color: Colors.grey[50], + child: QuestionAnswerScrollView(), + ), + ), + ),), + ], + ), + ), ); } @@ -52,24 +77,194 @@ class _FAQPageState extends State { } class QuestionAnswerScrollView extends StatelessWidget { - final List> questionsAndAnswers = [ - {'question': '1. What is the purpose of this app?', 'answer': 'The app provides official UAE statistics across categories like Economy, Environment, and Social. It allows users to explore datasets, view trends, and access detailed metrics to make informed decisions.'}, - {'question': '2. How do I bookmark a metric?', - 'answer': 'When viewing a metric, tap on the Bookmark icon. Your bookmarks are accessible from the My Bookmarks section in the navigation bar.'}, - {'question': '3.Can I view the app in another language?', 'answer': 'Yes, the app supports both English and Arabic. You can toggle the language using the switch in the top-right corner of the app.'}, - {'question': '4.What happens if I forget my password?','answer':'Tap on the Forgot Password? link on the login page. Follow the steps to reset your password via your registered email address.'}, - {'question':'5. Can I edit my profile details?','answer': 'Yes, you can edit specific fields such as Date of Birth and User Image. However, certain fields like Username and Email are non-editable for security reasons.'}, - {'question':'6.How often is the data updated?','answer':'The app updates its datasets regularly to ensure users have access to the latest statistics. Notifications are sent whenever significant updates are made.'}, - {'question':'7.Do I need to create an account to use the app?', - 'answer':"Yes, the app is strictly for registered and approved users. To access the data:You need to sign up using the Register Now option on the login screen .Your registration must be approved by the admin.Once approved, you can log in to access the app's features and datasets.Guest access is not available for this application."}, - {'question':'8.How can I navigate through the app?','answer':'Use the main categories on the homepage (e.g., Economy, Environment) to access subcategories and detailed KPIs.Drill down into specific metrics or graphical views by tapping on a KPI.Access additional features like Bookmarks or Profile through the navigation bar.'}, - {'question':'9.What types of data are available?','answer':'The app offers data on:Economy: GDP, growth rates, and economic trends.Environment: Electricity production, water consumption, and more.Social: Labor force distribution, participation rates, and demographic data.'}, - {'question':'10.What types of graphs and charts are available?','answer':'The app provides a variety of visualizations, including:Line charts for trends.Bar and column charts for comparisons.Stacked charts for multi-layered data views.'}, - ]; + const QuestionAnswerScrollView({super.key}); + @override Widget build(BuildContext context) { + final List> questionsAndAnswers = [ + {'question':'What is the purpose of this app?', 'answer': 'The app provides official UAE statistics across categories like Economy, Environment, and Social. It allows users to explore datasets, view trends, and access detailed metrics to make informed decisions.'}, + {'question':'Do I need to create an account to use the app?', 'answer':RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'Yes, the app is strictly for registered and approved users. To access the data:\n\n', + style: TextStyle(fontWeight: FontWeight.normal, color: Colors.grey), + ), + TextSpan( + text: ' 1. You need to sign up using the Register Now option on the login screen.\n', + style: TextStyle(color: Colors.grey), + ), + TextSpan( + text: ' 2. Your registration must be approved by the admin.\n', + style: TextStyle(color: Colors.grey), + ), + + TextSpan( + text: " 3. Once approved, you can log in to access the app's features and datasets.\n\nGuest access is not available for this application.\n ", + style: TextStyle(color: Colors.grey), + ), + ], + ), + ),}, + {'question':'How can I navigate through the app?','answer':'• Use the main categories on the homepage (e.g., Economy, Environment) to access subcategories and detailed KPIs.\n• Drill down into specific metrics or graphical views by tapping on a KPI.\n• Access additional features like Bookmarks or Profile through the navigation bar.\n'}, + {'question':'What types of data are available?', 'answer':RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'The app offers data on:\n\n', + style: TextStyle(color: Colors.grey), + ), + TextSpan( + text: ' • Economy', + style: TextStyle(color: Colors.black87,fontWeight: FontWeight.w500), + ), + TextSpan( + text: ' GDP, growth rates, and economic trends\n', + style: TextStyle(color: Colors.grey), + ), + + TextSpan( + text: ' • Environment:', + style: TextStyle(color: Colors.black87,fontWeight: FontWeight.w500), + ), + TextSpan( + text: ' Electricity production, water consumption, and more.\n', + style: TextStyle(color: Colors.grey), + ), + TextSpan( + text: ' • Social:', + style: TextStyle(color: Colors.black87,fontWeight: FontWeight.w500), + ), + TextSpan( + text: ' Labor force distribution, participation rates, and demographic data.\n', + style: TextStyle(color: Colors.grey), + ), + ], + ), + ),}, + {'question':'How do I bookmark a metric?', 'answer': RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'When viewing a metric, tap on the', + style: TextStyle(color: Colors.grey), + ), + TextSpan( + text: ' Bookmark ', + style: TextStyle(color: Colors.black87,fontWeight: FontWeight.w500), + ), + TextSpan( + text: 'icon. Your bookmarks are accessible from the', + style: TextStyle(color: Colors.grey), + ), + + TextSpan( + text: 'My Bookmarks', + style: TextStyle(color: Colors.black87,fontWeight: FontWeight.w500), + ), + TextSpan( + text: 'section in the navigation bar.\n', + style: TextStyle(color: Colors.grey), + ), + ], + ), + ),}, + {'question':'Can I view the app in another language?', 'answer': RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'Yes, the app supports both ', + style: TextStyle(color: Colors.grey), + ), + TextSpan( + text: 'English ', + style: TextStyle(color: Colors.black87,fontWeight: FontWeight.w500), + ), + TextSpan( + text: 'and', + style: TextStyle(color: Colors.grey), + ), + + TextSpan( + text: ' Arabic ', + style: TextStyle(color: Colors.black87,fontWeight: FontWeight.w500), + ), + TextSpan( + text: '. You can toggle the language using the switch in the top-right corner of the app.\n', + style: TextStyle(color: Colors.grey), + ), + ], + ), + ),}, + {'question':'What happens if I forget my password?','answer':'Tap on the Forgot Password? link on the login page. Follow the steps to reset your password via your registered email address.\n'}, + {'question':'Can I edit my profile details?','answer': 'Yes, you can edit specific fields such as Date of Birth and User Image. However, certain fields like Username and Email are non-editable for security reasons.\n'}, + {'question':'How often is the data updated?','answer':'The app updates its datasets regularly to ensure users have access to the latest statistics. Notifications are sent whenever significant updates are made.\n'}, + {'question':'What types of graphs and charts are available?','answer':'The app provides a variety of visualizations, including:\n • Line charts for trends.\n • Bar and column charts for comparisons.\n • Stacked charts for multi-layered data views.\n'}, + {'question':'Can I share the data or charts?','answer':RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'Yes, you can share data or charts directly from the app by tapping the ', + style: TextStyle(color: Colors.grey), + ), + TextSpan( + text: 'Share ', + style: TextStyle(color: Colors.black87,fontWeight: FontWeight.w500), + ), + TextSpan( + text: 'button available on most pages.\n', + style: TextStyle(color: Colors.grey), + ), + ], + ), + ),}, + {'question':'What should I do if I encounter an issue?','answer':RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'For technical support or feedback, navigate to the ', + style: TextStyle(color: Colors.grey), + ), + TextSpan( + text: 'Help ', + style: TextStyle(color: Colors.black87,fontWeight: FontWeight.w500), + ), + TextSpan( + text: 'section in the app and contact the support team.\n', + style: TextStyle(color: Colors.grey), + ), + ], + ), + ),}, + {'question':'How do I log out of the app?','answer':RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'Go to the ', + style: TextStyle(color: Colors.grey), + ), + TextSpan( + text: 'Profile ', + style: TextStyle(color: Colors.black87,fontWeight: FontWeight.w500), + ), + TextSpan( + text: 'section and tap the ', + style: TextStyle(color: Colors.grey), + ), + TextSpan( + text: 'Logout ', + style: TextStyle(color: Colors.black87,fontWeight: FontWeight.w500), + ), + TextSpan( + text: 'option in the top-right corner.\n', + style: TextStyle(color: Colors.grey), ), + ], + ), + ),}, + ]; + return SingleChildScrollView( child: Column( children: questionsAndAnswers.map((qa) => QuestionAnswerCard(qa: qa)).toList(), @@ -78,9 +273,10 @@ class QuestionAnswerScrollView extends StatelessWidget { } } -class QuestionAnswerCard extends StatefulWidget { - final Map qa; - QuestionAnswerCard({required this.qa}); +class QuestionAnswerCard extends StatefulWidget { // Use dynamic to accommodate RichText + + const QuestionAnswerCard({super.key, required this.qa}); + final Map qa; @override _QuestionAnswerCardState createState() => _QuestionAnswerCardState(); @@ -92,18 +288,18 @@ class _QuestionAnswerCardState extends State { @override Widget build(BuildContext context) { return Card( - color: Colors.white, + color: Colors.grey[50], elevation: 1, shape: RoundedRectangleBorder( borderRadius: BorderRadius.zero, // No rounding, it's a rectangle ), - margin: EdgeInsets.symmetric(vertical: 0,), + margin: EdgeInsets.symmetric(vertical: 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ListTile( dense: true, - textColor:Color(0xFF414042), + textColor: Color(0xFF414042), title: Text( widget.qa['question']!, style: TextStyle(fontWeight: FontWeight.w600, fontSize: 15), @@ -118,8 +314,10 @@ class _QuestionAnswerCardState extends State { if (isExpanded) Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Text( - widget.qa["answer"]!, + child: widget.qa['answer'] is RichText + ? widget.qa['answer'] + : Text( + widget.qa['answer'] ?? '', style: TextStyle(color: Colors.grey), ), ), diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/features.dart b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/features.dart index b4c0315c..9ad1c659 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/features.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/features.dart @@ -1,621 +1,560 @@ import 'package:flutter/material.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; -import 'custom_text.dart'; +import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/user_guide/custom_text.dart'; class UsingFeatures extends StatelessWidget { - final List> contentList = [ - { - "text": "1.Data-Driven Insights:", - "fontSize": 16.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.bold, - "isBulletPoint": false, - }, - { - "text": "1.Access detailed datasets across categories such as Economy, Environment, Social, and more.Explore statistics presented as smart metrics, graphs, and charts for better understanding.", - "fontSize": 13.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - { - "text": "2.Drilldown Navigation:", - "fontSize": 16.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.bold, - "isBulletPoint": false, - }, - { - "text": "Start from high-level categories such as “Economy” and drill down into detailed KPIs, sub-sections, and graphical views for a better understanding of each topic.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - { - "text": "3.Interactive Visualizations:", - "fontSize": 14.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.bold, - "isBulletPoint": false, - }, + const UsingFeatures({super.key}); - { - "text": "View trends and relationships with interactive graphs and charts, including bar graphs, line charts, and more.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": true, - }, - { - "text": "Secure User Access:", - "fontSize": 14.0, - "height":1.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.bold, - "isBulletPoint": true, - }, - - { - "text": "4.Available only to registered users whose accounts have been approved by an admin.Login ensures secure access to sensitive statistical information.", - "fontSize": 14.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - - - - { - "text": "5.Profile Customization:", - "fontSize": 14.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.bold, - "isBulletPoint": false, - }, - - { - "text": "Update your profile details, such as name, password, and region, to personalize your experience.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - - { - "text": "6.Bookmark:", - "fontSize": 14.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.bold, - "isBulletPoint": false, - }, - - { - "text": "Save frequently accessed metrics and datasets for quick reference via the Bookmarks section.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - - { - "text": "7.Bilingual Support:", - "fontSize": 14.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.bold, - "isBulletPoint": false, - }, - - { - "text": "Toggle between English and Arabic for ease of use.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - - { - "text": "Who Can Use the App?", - "fontSize": 18.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - - { - "text": "This app is intended for:", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - - { - "text": "Government officials requiring UAE statistics for decision-making.", - "fontSize": 14.0, - "height":0.9, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": true, - }, - { - "text": "Business professionals and researchers analyzing market trends.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "height":0.9, - "isBulletPoint": true, - }, - { - "text": "Students and educators needing reliable data for study and teaching.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "height":0.9, - "isBulletPoint": true, - }, - - { - "text": "Purpose of the App", - "fontSize": 16.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - - { - "text": "Provide official statistics on the UAE in an accessible format.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "height":0.9, - "isBulletPoint": true, - }, - { - "text": "Support decision-making and research with reliable, up-to-date data.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "height":0.9, - "isBulletPoint": true, - }, - { - "text": "Enable users to interact with data visually through graphs and metrics.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "height":0.9, - "isBulletPoint": true, - }, - - { - "text": "How to Use the App", - "fontSize": 16.0, - "color":Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - - { - "text": "1.Registration and Approval:", - "fontSize": 14.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - - { - "text": "Sign up for the app and wait for admin approval.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": true, - }, - - { - "text": "2.Login and Navigation:", - "fontSize": 14.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - { - "text": "Once approved, log in to access the app. Navigate through the categories on the home screen.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": true, - }, - { - "text": "3.Access Data:", - "fontSize": 14.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - { - "text": 'Drill down into specific categories like "Economy" or "Environment" to view detailed KPIs and visual insights.', - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": true, - }, - - { - "text": "4.Bookmark Metrics:", - "fontSize": 14.0, - "color":Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - { - "text": 'Save important statistics for easy reference.', - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": true, - }, - - { - "text": "Stay Updated", - "fontSize": 16.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - { - "text": 'The app is regularly updated with the latest datasets and features to ensure users have access to the most current information.', - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - - { - "text": "How to Change My Password", - "fontSize": 14.0, - "color":Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - - { - "text": "Changing your password helps keep your account secure. Follow these steps to update your password:", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - { - "text": "Steps to Change Password", - "fontSize": 16.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - { - "text": "1. Access the Change Password Page:", - "fontSize": 14.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - { - "text": 'From the Profile page, tap on the "Change Password" link at the bottom.', - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - - { - "text": "2. Enter Old Password:", - "fontSize": 14.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - - { - "text": "Enter your current password in the first field.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - - { - "text": "3. Set a New Password:", - "fontSize": 14.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - - { - "text": "Enter your new password in the second field.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": true, - }, - - { - "text": "Re-enter the new password in the third field for confirmation.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - - - { - "text": "4. Password Requirements:", - "fontSize": 14.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - - { - "text": "Your new password must be different from the previously used password.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - - { - "text": "5.Save Your Password", - "fontSize": 14.0, - "color":Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - - - { - "text": "Tap the Save button.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": true, - }, - - { - "text": "A confirmation message will appear: “Password has been successfully updated.”", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": true, - }, - { - "text": "6.Re-Login:", - "fontSize": 14.0, - "color":Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - - { - "text": "Log in again using your new password to continue using the app", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - - { - "text": "How to Edit My Profile", - "fontSize": 16.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - - { - "text": "Editing your profile allows you to update specific information while ensuring that critical details remain secure. Follow the steps below to update your profile:", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - - - { - "text": "Steps to Edit Profile", - "fontSize": 16.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - - { - "text": "1.Access the Profile Page:", - "fontSize": 14.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - - { - "text": 'Tap on the "Profile" option from the navigation bar at the top left corner of the screen.', - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - - { - "text": "2.Editable Fields:", - "fontSize": 14.0, - "color":Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - - { - "text": "Full Name: Tap the text box to enter your name (up to 40 alphanumeric characters).", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": true, - }, - - { - "text": "Date of Birth: Use the calendar dropdown to select your date of birth.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": true, - }, - - { - "text": "Country/Region: Choose your current location from the dropdown menu.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": true, - }, - - { - "text": "User Image: Tap on the avatar icon to upload or change your profile picture.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": true, - }, - - { - "text": "3.Non-Editable Fields:", - "fontSize": 14.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - - { - "text": "Username and Email ID: These fields cannot be edited to ensure the integrity of your account.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - - { - "text": "Agree to Terms and Conditions:", - "fontSize": 14.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - - { - "text": 'Ensure that the checkbox for "I agree to Terms & Conditions and Privacy Policy" is selected before saving changes.', - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": false, - }, - - { - "text": "Save Your Changes:", - "fontSize": 14.0, - "color": Color(0xFF414042), - "fontWeight": FontWeight.w600, - "isBulletPoint": false, - }, - - { - "text": "Tap the Save button at the bottom of the screen.", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": true, - }, - - { - "text": "A confirmation dialog will appear with the message:", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": true, - }, - - { - "text": "“Are you sure you want to save this page? Once saved, you will not be able to change your name or date of birth.”", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint":false , - }, - - - { - "text": "Select Confirm to save the changes or Cancel to go back", - "fontSize": 14.0, - "color": Color(0xFF898C81), - "fontWeight": FontWeight.normal, - "isBulletPoint": true, - }, - - - // Add more items here as needed - ]; @override Widget build(BuildContext context) { - return BaseScaffold( title: Text('Key Features'), - body: Scrollbar( - thumbVisibility: true, - thickness: 6, - radius: Radius.circular(10), - interactive: true, - child:SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.all(16.0), - width: MediaQuery.of(context).size.width, - child: Text( - "FEATURES", - style: TextStyle( - color: Color(0xFF265E84), // Text color - fontSize: 18, - fontWeight: FontWeight.w600 - ), - ), - ), - Container( - padding: const EdgeInsets.all(13.0), - color: Color(0xFFD9D9D9), - child:ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: contentList.length, - itemBuilder: (context, index) { - final item = contentList[index]; - return CustomContainer( - text: item["text"], - fontSize: item["fontSize"], - color: item["color"], - fontWeight: item["fontWeight"], - height:item["height"], - isBulletPoint: item["isBulletPoint"] ?? false, - textDecoration: item["textDecoration"] ?? TextDecoration.none, - decorationColor: item["decorationColor"], - ); - }, - ), - ) - ], + + List textSpans = [ + CustomTextRich.createTextSpan('1.Data-Driven Insights:', + fontSize: 15, + color: Colors.black87, + fontWeight: FontWeight.bold, + height: 2, + ), + CustomTextRich.createTextSpan( + 'Access detailed datasets across categories such as Economy, Environment, Social, ', + fontSize: 13, color:Color(0xFF898C81), + fontWeight: FontWeight.normal , + spacingTop: 1.9,), + + CustomTextRich.createTextSpan( + ' Economy, Environment, Social ', + fontSize: 13, color:Colors.black, + fontWeight: FontWeight.w600 , + ), + CustomTextRich.createTextSpan( + 'and more.Explore statistics presented as smart metrics, graphs, and charts for better understanding.', + fontSize: 13, color:Color(0xFF898C81), + fontWeight: FontWeight.normal , + ), + + CustomTextRich.createTextSpan('\n2.Drilldown Navigation:', fontSize: 15, + color: Colors.black87, + fontWeight: FontWeight.w600, + height: 2 + ), + + CustomTextRich.createTextSpan( + '\nStart with high-level categories and drill down to specific KPIs and metrics for deeper insights.', + fontSize: 13, color:Color(0xFF898C81), + fontWeight: FontWeight.normal , + ), + CustomTextRich.createTextSpan('\n3.Interactive Visualizations:', fontSize: 15, + color: Colors.black87, + fontWeight: FontWeight.w600, + height: 2 + ), + + CustomTextRich.createTextSpan( + '\nStart with high-level categories and drill down to specific KPIs and metrics for deeper insights.', + fontSize: 13, color:Color(0xFF898C81), + fontWeight: FontWeight.normal , + ), + CustomTextRich.createTextSpan( + 'View trends and relationships with interactive graphs and charts, including bar graphs, line charts, and more.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n4.Secure User Access:', + fontSize: 14.0, + color: Colors.black87, + fontWeight: FontWeight.bold, + height: 2, + ), + CustomTextRich.createTextSpan( + '\nAvailable only to registered users whose accounts have been approved by an admin. Login ensures secure access to sensitive statistical information.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n5.Profile Customization:', + fontSize: 14.0, + color: Color(0xFF414042), + fontWeight: FontWeight.bold, + height: 2 + ), + CustomTextRich.createTextSpan( + '\nUpdate your profile details, such as name, password, and region, to personalize your experience.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n6.Bookmark:', + fontSize: 14.0, + color: Color(0xFF414042), + fontWeight: FontWeight.bold, + ), + CustomTextRich.createTextSpan( + '\nSave frequently accessed metrics and datasets for quick reference via the Bookmarks section.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n7.Bilingual Support:', + fontSize: 14.0, + color: Color(0xFF414042), + fontWeight: FontWeight.bold, + height: 2 + ), + CustomTextRich.createTextSpan( + '\nToggle between English and Arabic for ease of use.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\nWho Can Use the App?', + fontSize: 18.0, + color: Color(0xFF414042), + fontWeight: FontWeight.w600, + height: 2.5 + ), + CustomTextRich.createTextSpan( + '\nThis app is intended for:', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n\n • Government officials requiring UAE statistics for decision-making.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n •Business professionals and researchers analyzing market trends.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n •Students and educators needing reliable data for study and teaching.\n\n', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + 'Purpose of the App\n', + fontSize: 16.0, + color: Color(0xFF414042), + fontWeight: FontWeight.w600, + height: 2.0 + ), + CustomTextRich.createTextSpan( + ' • Provide official statistics on the UAE in an accessible format.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n • Support decision-making and research with reliable, up-to-date data.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n • Enable users to interact with data visually through graphs and metrics.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\nHow to Use the App', + fontSize: 18.0, + color: Color(0xFF414042), + fontWeight: FontWeight.w600, + height: 2.3 + ), + CustomTextRich.createTextSpan( + '\n1.Registration and Approval:', + fontSize: 15.0, + color: Colors.black87, + fontWeight: FontWeight.bold, + height: 2.0 + ), + CustomTextRich.createTextSpan( + '\nSign up for the app and wait for admin approval.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n2.Login and Navigation:', + fontSize: 15.0, + color: Color(0xFF414042), + fontWeight: FontWeight.bold, + height: 2.0, + ), + CustomTextRich.createTextSpan( + '\nOnce approved, log in to access the app. Navigate through the categories on the home screen.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n3.Access Data:', + fontSize: 15.0, + color: Color(0xFF414042), + fontWeight: FontWeight.bold, + height: 2.0, + ), + CustomTextRich.createTextSpan( + '\nDrill down into specific categories like "Economy" or "Environment" to view detailed KPIs and visual insights.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n4.Bookmark Metrics:', + fontSize: 15.0, + color: Color(0xFF414042), + fontWeight: FontWeight.bold, + height: 2.0, + ), + CustomTextRich.createTextSpan( + '\nSave important statistics for easy reference.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\nStay Updated', + fontSize: 18.0, + color: Color(0xFF414042), + fontWeight: FontWeight.w600, + height: 3, + ), + CustomTextRich.createTextSpan( + '\nThe app is regularly updated with the latest datasets and features to ensure users have access to the most current information.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\nHow to Change My Password', + fontSize: 18.0, + color: Colors.black87, + fontWeight: FontWeight.w600, + height: 3, + ), + CustomTextRich.createTextSpan( + '\nChanging your password helps keep your account secure. Follow these steps to update your password:\n \n', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + 'Steps to Change Password', + fontSize: 16.0, + color: Color(0xFF414042), + fontWeight: FontWeight.bold, + height: 2.5 + ), + CustomTextRich.createTextSpan( + '\n1.Access the Change Password Page:', + fontSize: 14.0, + color: Color(0xFF414042), + fontWeight: FontWeight.bold, + height: 1.5, + ), + CustomTextRich.createTextSpan( + '\nFrom the Profile page, tap on the "Change Password" link at the bottom.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n2.Enter Old Password:', + fontSize: 14.0, + color: Color(0xFF414042), + fontWeight: FontWeight.bold, + height: 1.5, + ), + CustomTextRich.createTextSpan( + '\nEnter your current password in the first field.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n3.Set a New Password:', + fontSize: 14.0, + color: Color(0xFF414042), + fontWeight: FontWeight.bold, + height: 1.5, + ), + CustomTextRich.createTextSpan( + '\n• Enter your new password in the second field.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n• Re-enter the new password in the third field for confirmation.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n4.Password Requirements:', + fontSize: 14.0, + color: Color(0xFF414042), + fontWeight: FontWeight.bold, + ), + CustomTextRich.createTextSpan( + '\nYour new password must be different from the previously used password.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n5.Save Your Password:', + fontSize: 14.0, + color: Color(0xFF414042), + fontWeight: FontWeight.bold, + ), + CustomTextRich.createTextSpan( + '\n• Tap the Save button.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n• A confirmation message will appear: “Password has been successfully updated.”', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n6.Re-Login:', + fontSize: 14.0, + color: Color(0xFF414042), + fontWeight: FontWeight.bold, + ), + CustomTextRich.createTextSpan( + '\nLog in again using your new password to continue using the app.\n\n', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + 'How to Edit My Profile', + fontSize: 18.0, + color: Color(0xFF414042), + fontWeight: FontWeight.w600, + height: 2.5, + ), + CustomTextRich.createTextSpan( + '\nEditing your profile allows you to update specific information while ensuring that critical details remain secure. Follow the steps below to update your profile:\n', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\nSteps to Edit Profile', + fontSize: 17.0, + color: Color(0xFF414042), + fontWeight: FontWeight.bold, + height: 2, + ), + CustomTextRich.createTextSpan( + '\n 1.Access the Profile Page:', + fontSize: 14.0, + color: Colors.black87, + fontWeight: FontWeight.w600, + height: 2, + ), + CustomTextRich.createTextSpan( + '\nTap on the "Profile" option from the navigation bar at the top left corner of the screen.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n 2. Editable Fields:', + fontSize: 14.0, + color: Colors.black87, + fontWeight: FontWeight.w600, + height: 2, + ), + CustomTextRich.createTextSpan( + '\n • Full Name:', + fontSize: 14.0, + color: Color(0xFF414042), + fontWeight: FontWeight.w500, + ), + CustomTextRich.createTextSpan( + ' Tap the text box to enter your name (up to 40 alphanumeric characters).', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n • Date of Birth:', + fontSize: 14.0, + color: Color(0xFF414042), + fontWeight: FontWeight.w500, + ), + CustomTextRich.createTextSpan( + ' Use the calendar dropdown to select your date of birth.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n • Country/Region: ', + fontSize: 14.0, + color: Color(0xFF414042), + fontWeight: FontWeight.w500, + ), + CustomTextRich.createTextSpan( + 'Choose your current location from the dropdown menu.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n •User Image: ', + fontSize: 14.0, + color: Color(0xFF414042), + fontWeight: FontWeight.w500, + ), + CustomTextRich.createTextSpan( + 'Tap on the avatar icon to upload or change your profile picture.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n 3. Non-Editable Fields:', + fontSize: 14.0, + color: Colors.black87, + fontWeight: FontWeight.w600, + height: 2, + ), + CustomTextRich.createTextSpan( + '\nUsername ', + fontSize: 14.0, + color: Color(0xFF414042), + fontWeight: FontWeight.w500, + ), + CustomTextRich.createTextSpan( + 'and ', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + 'Email ID: ', + fontSize: 14.0, + color: Color(0xFF414042), + fontWeight: FontWeight.w500, + ), + CustomTextRich.createTextSpan( + 'These fields cannot be edited to ensure the integrity of your account.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n 4.Agree to Terms and Conditions:', + fontSize: 14.0, + color: Colors.black87, + fontWeight: FontWeight.w600, + height: 2, + ), + + CustomTextRich.createTextSpan( + '\nEnsure that the checkbox for "I agree to Terms & Conditions and Privacy Policy" is selected before saving changes.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n 5. Save Your Changes:', + fontSize: 15.0, + color: Colors.black87, + fontWeight: FontWeight.bold, + height: 2, + ), + CustomTextRich.createTextSpan( + '\n • Tap the Save button at the bottom of the screen.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n • A confirmation dialog will appear with the message:', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '“Are you sure you want to save this page? Once saved, you will not be able to change your name or date of birth.”', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + CustomTextRich.createTextSpan( + '\n • Select Confirm to save the changes or Cancel to go back.', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + + ]; + return + BaseScaffold( title: Text('Key Features', + style: TextStyle( + color: Color(0xFFAA8E83), + ),), + showBackIcon: true, + body:SafeArea(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.all(16.0), + width: MediaQuery.of(context).size.width, + child: Text( + 'FEATURES', + style: TextStyle( + color: Colors.black,// Text color + fontSize: 18, + fontWeight: FontWeight.w500, + ), ), ), - ), - ); + Expanded(child: Scrollbar( + thumbVisibility: true, + thickness: 6, + radius: Radius.circular(10), + interactive: true, + child: + Container( + color: Colors.grey[50], // Set the background color here + // child: SingleChildScrollView( + child: CustomTextRich( + textSpans: textSpans, + padding: const EdgeInsets.all(16.0), + textAlign: TextAlign.start, + ), + ), + ),), + ], + ), ), + ); } } diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/getting_started.dart b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/getting_started.dart index cd911638..ad4510b6 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/getting_started.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/getting_started.dart @@ -1,3 +1,4 @@ +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -5,152 +6,174 @@ import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/user_g import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; import 'package:uae_stat/infrastructure/services/img_asset_paths/banner_asset_path.dart'; +import 'package:url_launcher/url_launcher.dart'; class GettingStarted extends StatelessWidget { + GettingStarted({super.key}); + final String url = 'https://fcsc.gov.ae/en-us/Pages/home.aspx'; final fcscBanner = Image.asset( BannerAssetPath.fcsc, height: 56, alignment:Alignment.center, ); - final List> contentList = [ - { - 'text': 'This app provides a centralized platform to access key statistical data and insights about the UAE, empoweringusers with accurate and up-to-date information on various sectors.', - 'fontSize': 16.0, - 'color': Color(0xFF898C81), - 'fontWeight': FontWeight.normal, - 'isBulletPoint': false, - }, - { - 'text': 'How to Get Started', - 'fontSize': 18.0, - 'color': Color(0xFF414042), - 'fontWeight': FontWeight.w600, - 'isBulletPoint': false, - }, - { - 'text': '1.Download the app from the App Store or Google Play Store.', - 'fontSize': 14.0, - 'color': Color(0xFF898C81), - 'fontWeight': FontWeight.normal, - 'isBulletPoint': false, - }, - { - 'text': '2.Log in to unlock advanced features or continue as a guest.', - 'fontSize': 14.0, - 'color': Color(0xFF898C81), - 'fontWeight': FontWeight.normal, - 'isBulletPoint': false, - }, - { - 'text': '3.Navigate through categories, explore metrics, and bookmark essential information.', - 'fontSize': 14.0, - 'color': Color(0xFF898C81), - 'fontWeight': FontWeight.normal, - 'isBulletPoint': false, - }, - { - 'text': 'Stay Updated', - 'fontSize': 18.0, - 'color': Color(0xFF414042), - 'fontWeight': FontWeight.bold, - 'isBulletPoint': false, - }, - { - 'text': 'The app regularly updates datasets and features to reflect the latest information. Notifications will alert you about new data or improvements.', - 'fontSize': 14.0, - 'color': Color(0xFF898C81), - 'fontWeight': FontWeight.normal, - 'isBulletPoint': false, - }, - ]; + + @override Widget build(BuildContext context) { - return BaseScaffold(title: Text ('Getting Started'), - body: Scrollbar( - thumbVisibility: true, - thickness: 6, - radius: Radius.circular(10), - interactive: true, - child:SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.all(16.0), - width: MediaQuery.of(context).size.width, - child: Text( - 'WELCOME TO THE FEDERAL COMPETITIVENESS AND STATISTICS CENTRE (FCSC) MOBILE APPLICATION.', - style: TextStyle( - color: Color(0xFF265E84), // Text color - fontSize: 18, - fontWeight: FontWeight.w600 - ), - ), - ), - Container( - padding: const EdgeInsets.all(16.0), - color: Color(0xFFD9D9D9), - child:ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: contentList.length, - itemBuilder: (context, index) { - final item = contentList[index]; - return CustomContainer( - text: item['text'], - fontSize: item['fontSize']?? 20, - color: item['color'], - fontWeight: item['fontWeight'], - height:item['height'], - isBulletPoint: item['isBulletPoint'] ?? false, - textDecoration: item['textDecoration'] ?? TextDecoration.none, - decorationColor: item['decorationColor'], - ); - }, - ), - ), - Container( - padding: const EdgeInsets.all(16.0), - width: MediaQuery.of(context).size.width, - color: Color(0xFFD9D9D9), - child: Text.rich( - TextSpan( - children: [ - TextSpan( - text: 'For more information please visit ', - style: TextStyle( - fontSize: 16.0, - color: Color(0xFF265E84), - fontWeight: FontWeight.normal, - ), - ), + List textSpans = [ - TextSpan( - text: 'www.fcsc.gov.ae', - style: TextStyle( - fontSize: 16.0, - color: Color(0xFF568898), - fontWeight: FontWeight.normal, - decoration: TextDecoration.underline, - decorationColor:Color(0xFF568898), - ), - ), - ], + CustomTextRich.createTextSpan('How to Get Started\n', fontSize: 17, + color: Colors.black87, + fontWeight: FontWeight.bold, + ), + CustomTextRich.createTextSpan('1.Download the app from the App Store or Google Play Store.', + fontSize: 13, color:Color(0xFF898C81), + fontWeight: FontWeight.normal , + spacingTop: 1.9, + ), + CustomTextRich.createTextSpan( + '2.Log in to unlock advanced features or continue as a guest.\n', + fontSize: 13, color:Color(0xFF898C81), + fontWeight: FontWeight.normal , + spacingTop: 1.9,), + + CustomTextRich.createTextSpan( + '3.Navigate through categories, explore metrics, and bookmark essential information.\n', + fontSize: 13, color:Color(0xFF898C81), + fontWeight: FontWeight.normal , + ), + + CustomTextRich.createTextSpan('\nStay Updated', fontSize: 15, + color: Colors.black87, + fontWeight: FontWeight.w600, + height: 2, + ), + + CustomTextRich.createTextSpan( + '\nThe app regularly updates datasets and features to reflect the latest information. Notifications will alert you about new data or improvements.', + fontSize: 13, color:Color(0xFF898C81), + fontWeight: FontWeight.normal , + ), + + CustomTextRich.createTextSpan( + 'Support decision-making and research with reliable, up-to-date data.\n \n ', + fontSize: 14.0, + color: Color(0xFF898C81), + fontWeight: FontWeight.normal, + ), + + + ]; + return BaseScaffold( title: Text('About the App', + style: TextStyle( + color:Color(0xFF265E84), + fontWeight: FontWeight.w500, + ), + ), + showBackIcon: true, + body:SafeArea(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.all(16.0), + width: MediaQuery.of(context).size.width, + child: Text.rich( + TextSpan( + text: 'The', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w600, + color: Colors.grey, + ), + children: [ + TextSpan( + text:' Federal Competitiveness and Statistics Centre (FCSC),', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w600, + color: Colors.black87, ), - ), ), - Container( - color: Color(0xFFD9D9D9), - padding: const EdgeInsets.all(16.0), - width: MediaQuery.of(context).size.width, - child: Center( child: fcscBanner,), + TextSpan( + text:' in your App is designed to provide registered and approved users with access to accurate and comprehensive statistics about the UAE. The app serves as a centralized platform for exploring key datasets, trends, and insights across various sectors.', + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w600, + color: Colors.grey, + ), ), ], ), ), ), + Expanded(child: Scrollbar( + thumbVisibility: true, + thickness: 6, + radius: Radius.circular(10), + interactive: true, + child: SingleChildScrollView( + child: Column( + children: [ + Container( + color: Colors.grey[50], // Set the background color here + // child: SingleChildScrollView( + child: CustomTextRich( + textSpans: textSpans, + padding: const EdgeInsets.all(16.0), + textAlign: TextAlign.start, + ), + ), + Container( + color: Colors.grey[50], + padding: EdgeInsets.only(bottom: 26.0), + child:Text.rich( + TextSpan( + text: 'For more information please visit ', + style: TextStyle( + fontSize: 16.0, + color: Color(0xFF265E84), + fontWeight: FontWeight.normal,), + children: [ + TextSpan( + text: 'www.fcsc.gov.ae', + style: TextStyle( + fontSize: 16.0, + color: Color(0xFF568898), + fontWeight: FontWeight.normal, + decoration: TextDecoration.underline, + decorationColor:Color(0xFF568898), + ), + recognizer: TapGestureRecognizer() + ..onTap = () async { + final Uri uri = Uri.parse(url); + if (await launchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } else { + throw 'Could not launch $url'; + } + }, + ), + ], + ), + ), + ), + Container( + color: Colors.grey[50], + padding: EdgeInsets.only(bottom: 26.0), + width: MediaQuery.of(context).size.width, + child: Center( + child: fcscBanner, + ), + ), + ], + ), + ), + + ),), + ], + ), ), ); } } diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/user_guide.dart b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/user_guide.dart index de257834..c4086555 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/user_guide.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/user_guide/user_guide.dart @@ -43,14 +43,10 @@ class _UserGuideState extends State { // Each pair defines a row of (Color, Text) pairs [ {'routePath':'gettingStarted','color': Color(0xFF90B0D5), 'text': 'Getting Started', 'icon': 'START', 'index': '1'}, - {'routePath':'features','color': Color(0xFF497B8C), 'text': 'Account & Profile', 'icon': Icons.person_outline_rounded, 'index': '2'}, + {'routePath':'features','color': Color(0xFFAA8E83), 'text': 'Using Features', 'icon': Icons.stars_outlined, 'index': '2'}, ], [ - {'routePath':'features','color': Color(0xFFAA8E83), 'text': 'Using Features', 'icon': Icons.stars_outlined, 'index': '3'}, - {'routePath':'features','color': Color(0xFF265E84), 'text': 'Contact', 'icon': Icons.local_phone_outlined, 'index': '4'}, - ], - [ - {'routePath':'faq','color': Color(0xFF7296BE), 'text': 'FAQs', 'icon': Icons.question_answer_sharp, 'index': '5'}, + {'routePath':'faq','color': Color(0xFF7296BE), 'text': 'FAQs', 'icon': Icons.question_answer_sharp, 'index': '3'}, // {'routePath':'features','color': Color(0xFF7DAFBC), 'text': 'Advanced Settings', 'icon': Icons.settings_outlined, 'index': '6'}, ], ]), diff --git a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart index 52fc756b..7bb46425 100644 --- a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart +++ b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart @@ -5,16 +5,18 @@ class BaseScaffold extends StatelessWidget { final Widget body; final Widget title; final List? actions; + final bool showBackIcon; + final VoidCallback? onBackIconPressed; const BaseScaffold({ Key? key, required this.body, required this.title, this.actions, + this.showBackIcon=false, + this.onBackIconPressed, }) : super(key: key); - // final Widget body; - // - // const BaseScaffold({required this.body}); + @override Widget build(BuildContext context) { @@ -23,9 +25,29 @@ class BaseScaffold extends StatelessWidget { double myheight = MediaQuery.of(context).size.height; double mywidth = MediaQuery.of(context).size.width; return Scaffold( - appBar: AppBar(title: title, actions: [ + appBar: AppBar(title: title, + leading:showBackIcon + ? IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.arrow_back_ios_new_sharp), + onPressed: onBackIconPressed ?? + () { + Navigator.of(context).pop(); // Default back button behavior + }, + ) : null, + actions: [ IconButton(onPressed: () {}, icon: Icon(Icons.toggle_off_outlined)), - ]), + if (showBackIcon) + Builder( + builder: (context) => IconButton( + padding: EdgeInsets.zero, + icon: const Icon(Icons.menu), + onPressed: () { + Scaffold.of(context).openDrawer(); // Opens the drawer + }, + ), + ), + ],), drawer: Drawer( child: ListView( children: [ diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 89ab99dd..436f106b 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -10,6 +10,7 @@ import flutter_secure_storage_macos import path_provider_foundation import share_plus import shared_preferences_foundation +import url_launcher_macos import video_player_avfoundation import webview_flutter_wkwebview @@ -19,6 +20,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) FLTWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "FLTWebViewFlutterPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index cc69ece0..7f0df837 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "76.0.0" + version: "72.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.3" + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "6.7.0" analyzer_plugin: dependency: transitive description: @@ -186,10 +186,10 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.18.0" convert: dependency: transitive description: @@ -266,10 +266,10 @@ packages: dependency: transitive description: name: custom_lint_visitor - sha256: bfe9b7a09c4775a587b58d10ebb871d4fe618237639b1e84d5ec62d7dfef25f9 + sha256: "8aeb3b6ae2bb765e7716b93d1d10e8356d04e0ff6d7592de6ee04e0dd7d6587d" url: "https://pub.dev" source: hosted - version: "1.0.0+6.11.0" + version: "1.0.0+6.7.0" dart_style: dependency: transitive description: @@ -773,18 +773,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -813,10 +813,10 @@ packages: dependency: transitive description: name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" url: "https://pub.dev" source: hosted - version: "0.1.3-main.0" + version: "0.1.2-main.4" mailer: dependency: "direct main" description: @@ -1172,7 +1172,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.0" + version: "0.0.99" smart_api_client: dependency: "direct main" description: @@ -1216,10 +1216,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.11.1" state_notifier: dependency: transitive description: @@ -1248,10 +1248,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.2.0" syncfusion_flutter_charts: dependency: "direct main" description: @@ -1280,10 +1280,10 @@ packages: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.2" the_validator: dependency: "direct main" description: @@ -1316,6 +1316,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + url: "https://pub.dev" + source: hosted + version: "6.3.14" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + url: "https://pub.dev" + source: hosted + version: "6.3.2" url_launcher_linux: dependency: transitive description: @@ -1324,6 +1348,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" + source: hosted + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: @@ -1408,10 +1440,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.2.5" watcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a223630d..133d2b6d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,6 +22,7 @@ dependencies: cupertino_icons: ^1.0.6 fl_chart: ^0.69.0 marquee: ^2.2.3 + url_launcher: ^6.3.1 # dynamic_layouts: # git: # url: https://github.com/flutter/packages.git