App Tour Bug and Bookmark Frontend added

This commit is contained in:
Kalonkarthik 2025-02-03 15:13:38 +05:30
commit ceeb873797
17 changed files with 1153 additions and 636 deletions

View File

@ -15,12 +15,12 @@ if (localPropertiesFile.exists()) {
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
if (flutterVersionCode == null) {
flutterVersionCode = "3"
flutterVersionCode = "6"
}
def flutterVersionName = localProperties.getProperty("flutter.versionName")
if (flutterVersionName == null) {
flutterVersionName = "1.0.2"
flutterVersionName = "1.0.5"
}
def keystorePropertiesFile = rootProject.file("key.properties")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 558 B

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -414,7 +414,9 @@ final GoRouter router = GoRouter(
builder: (context, state) {
final userId = state.pathParameters['userId']!;
final email = state.pathParameters['email']!;
return CreateNewPw(userId: userId, email: email);
final key = state.uri.queryParameters['key'] ?? '';
return CreateNewPw(userId: userId, email: email, keyParam: key);
},
),
GoRoute(

View File

@ -0,0 +1,7 @@
import 'package:flutter/material.dart';
abstract class UaeNumbersAssetPath {
static const _basePath = 'assets/icons/uae_numbers';
static const bookmarksUae = '$_basePath/bookmarks.png';
static const shareUae = '$_basePath/share.png';
}

View File

@ -13,7 +13,9 @@ import '../../../config/my_theme.dart';
class CreateNewPw extends StatefulWidget {
final String userId;
final String email;
const CreateNewPw({Key? key, required this.userId, required this.email});
final String? keyParam;
const CreateNewPw({Key? key, required this.userId, required this.email, required this.keyParam});
@override
State<CreateNewPw> createState() => _CreateNewPwState();
@ -210,7 +212,14 @@ class _CreateNewPwState extends State<CreateNewPw> {
alignment: Alignment.topRight,
child: GestureDetector(
onTap: () {
context.go('/editProfile');
if (widget.keyParam == 'editProfile') {
context.go('/editProfile');
}else
{
context.go('/profile/${widget.userId}');
}
},
child: Container(
margin: EdgeInsets.only(top: 16,left: 16,bottom: 16,right: 1), // Add margin for positioning
@ -351,8 +360,8 @@ class _CreateNewPwState extends State<CreateNewPw> {
SizedBox(height: screenHeight / 5),
Center(
child: Container(
height: screenHeight / 8,
width: screenWidth / 2,
height: screenHeight / 12,
width: screenWidth / 2.5,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/splash_screen/logo.png"),

View File

@ -31,6 +31,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
String? _password;
bool registrationSuccess = false;
bool registrationFailed = false;
bool isRegistering = false;
dynamic userID;
final pb = PocketBase('https://pb.venbait.in');
@ -144,6 +145,12 @@ class _RegisterScreenState extends State<RegisterScreen> {
final regex = RegExp(
r'^[A-Za-z0-9~!@#$%^&*()_+=[\]{}|;:,.<>?/-àèìòùáéíóúÀÈÌÒÙÁÉÍÓÚ]*$');
// Define regular expressions for password complexity requirements
final hasUppercase = RegExp(r'[A-Z]');
final hasLowercase = RegExp(r'[a-z]');
final hasDigit = RegExp(r'\d');
final hasSpecialCharacter = RegExp(r'[~!@#$%^&*()_+=[\]{}|;:,.<>?/-]');
if (value == null || value.isEmpty) {
return 'Required';
} else if (value.length < 8) {
@ -151,7 +158,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
}
// Check the length constraint
if (value.length < 8 || value.length > 40) {
if (value.length < 8 || value.length > 64) {
return 'Password must be between 8 and 64 characters';
}
@ -160,6 +167,27 @@ class _RegisterScreenState extends State<RegisterScreen> {
return 'Password contains invalid characters';
}
// Track missing constraints
List<String> missingConstraints = [];
if (!hasUppercase.hasMatch(value)) {
missingConstraints.add('uppercase letter');
}
if (!hasLowercase.hasMatch(value)) {
missingConstraints.add('lowercase letter');
}
if (!hasDigit.hasMatch(value)) {
missingConstraints.add('numeric digit');
}
if (!hasSpecialCharacter.hasMatch(value)) {
missingConstraints.add('special character');
}
// If there are missing constraints, return a consolidated message
if (missingConstraints.isNotEmpty) {
return 'At least one ${missingConstraints.join(', ')}';
}
_password = value; // Store the password for confirm password validation
return null;
}
@ -185,12 +213,15 @@ class _RegisterScreenState extends State<RegisterScreen> {
Future<void> _registerUser() async {
if (_formKey.currentState?.validate() ?? false) {
if (isChecked) {
setState(() {
isRegistering = true; // Disable the button
});
try {
final adminAuth = await pb.admins
.authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com');
final adminToken = adminAuth.token;
print('adminToken- ${adminToken}');
print('adminTokenREgistration- ${adminToken}');
// Create user in PocketBase
final response = await pb.collection('users').create(body: {
'uname': _usernameController.text,
@ -211,6 +242,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
userID = response.id;
registrationSuccess = true;
registrationFailed = false; // Show success message on success
isRegistering = false;
});
// Navigate to ProfileScreen after successful registration
@ -249,6 +281,9 @@ class _RegisterScreenState extends State<RegisterScreen> {
registrationFailed = true;
registrationSuccess = false;
});
setState(() {
isRegistering = false; // Re-enable the button after error
});
}
}
} else {
@ -330,7 +365,9 @@ class _RegisterScreenState extends State<RegisterScreen> {
buildIconContainer(Icons.report, Color(0xFF7DAFBC)),
SizedBox(height: 20),
Text(
"Your registration is pending for verification.",
context.translate(
'Your registration is pending for verification.',
'تسجيلك في انتظار التحقق.'),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
@ -339,7 +376,9 @@ class _RegisterScreenState extends State<RegisterScreen> {
),
SizedBox(height: 10),
Text(
"Kindly verify your mail to proceed further.",
context.translate(
'Kindly verify your mail to proceed further.',
'يرجى التحقق من البريد الخاص بك للمضي قدما.'),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
@ -350,14 +389,14 @@ class _RegisterScreenState extends State<RegisterScreen> {
ElevatedButton(
onPressed: () => {context.go('/')},
child: Text(
'Go to Login',
context.translate('Go to Login', 'اذهب إلى تسجيل الدخول'),
),
),
SizedBox(height: screenheight / 5),
Center(
child: Container(
height: screenheight / 8,
width: screenwidth / 2,
height: screenheight / 16,
width: screenwidth / 2.5,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
@ -379,7 +418,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
buildIconContainer(Icons.report, Colors.red),
SizedBox(height: 20),
Text(
"Sorry ${_usernameController.text}!",
context.translate('Sorry ${_usernameController.text}!',
'آسف ${_usernameController.text}!'),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
@ -388,7 +428,9 @@ class _RegisterScreenState extends State<RegisterScreen> {
),
SizedBox(height: 10),
Text(
"Your registration process failed. For further assistance, please contact support.",
context.translate(
'Your registration process failed. For further assistance, please contact support.',
'فشلت عملية التسجيل الخاصة بك. لمزيد من المساعدة، يرجى الاتصال بالدعم.'),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
@ -409,14 +451,14 @@ class _RegisterScreenState extends State<RegisterScreen> {
})
},
child: Text(
'Retry',
context.translate('Retry', 'أعد المحاولة'),
),
),
SizedBox(height: screenheight / 5),
Center(
child: Container(
height: screenheight / 8,
width: screenwidth / 2,
height: screenheight / 16,
width: screenwidth / 2.5,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
@ -475,7 +517,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
decoration: InputDecoration(
// hintText: 'Enter your email',
hintText: _showHints[1]
? AppLocalizations.of(context)!.enter_your_email
? AppLocalizations.of(context)!
.enter_your_email
: null,
// _showHints[1] ? 'Enter your email' : null,
prefixIcon: Icon(
@ -500,7 +543,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
obscureText: _obscurePassword,
decoration: InputDecoration(
hintText: _showHints[2]
? AppLocalizations.of(context)!.enter_your_password
? AppLocalizations.of(context)!
.enter_your_password
: null,
// _showHints[2] ? 'Enter your password' : null,
prefixIcon: Icon(
@ -510,8 +554,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility
: Icons.visibility_off,
? Icons.visibility_off
: Icons.visibility,
color: Colors.blue,
),
onPressed: () {
@ -545,8 +589,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
suffixIcon: IconButton(
icon: Icon(
_obscureConfirmPassword
? Icons.visibility
: Icons.visibility_off,
? Icons.visibility_off
: Icons.visibility,
color: Colors.blue,
),
onPressed: () {
@ -632,7 +676,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
SizedBox(
width: screenwidth / 1.3,
child: ElevatedButton(
onPressed: _registerUser,
onPressed: isRegistering ? null : _registerUser,
style: ElevatedButton.styleFrom(
backgroundColor: Color(
0xFFA7887A), // Brownish color for Register
@ -719,8 +763,8 @@ class _RegisterScreenState extends State<RegisterScreen> {
),
Center(
child: Container(
height: screenheight / 8,
width: screenwidth / 2,
height: screenheight / 16,
width: screenwidth / 2.5,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(

View File

@ -4,9 +4,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
import 'package:uae_stat/domain/use_cases/language.dart';
import 'package:uae_stat/infrastructure/services/img_asset_paths/icons/uae_numbers_asset_path.dart';
import 'package:uae_stat/presentation/Screens/charts/services/api_service.dart';
import 'package:uae_stat/presentation/Screens/charts/widgets/chart_widget.dart';
import 'package:uae_stat/presentation/components/indicators/locale_provider.dart';
import 'package:uae_stat/presentation/components/my_toggle.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:uae_stat/presentation/Screens/app_tour/Target_content.dart';
@ -761,8 +763,8 @@ class _ChartScreen1State extends ConsumerState<ChartScreen1> {
filterDataSet = filterData.entries
.map((entry) => {'key': entry.key, 'value': entry.value})
.toList();
// print("filterDataf1:- $filterData");
// print("filterDataf11:- $filterDataSet");
print("filterDataf1:- $filterData");
print("filterDataf11:- $filterDataSet");
} else {
filterDataSet = data['filterData'] ?? [];
print('filterData not found!');
@ -1081,53 +1083,139 @@ class _ChartScreen1State extends ConsumerState<ChartScreen1> {
),
),
// 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(originalTabChartsData);
// cardData = List.from(originalTabCardData);
// });
// // Reset the selectedFiltersStorage to empty when clearing
// selectedFiltersStorage.clear();
// Navigator.pop(context);
// },
// child: Text(context.translate(
// 'Clear',
// 'واضح',
// )),
// style: ElevatedButton.styleFrom(
// backgroundColor: Colors.grey,
// ),
// ),
// ElevatedButton(
// onPressed: () {
// print(
// "Selected Filters before applying: $selectedFilters");
// setState(() {
// chartsData = originalTabChartsData;
// cardData = originalTabCardData;
// });
//
// applyFilters(context, filters, chartsData, cardData,
// selectedFilters);
// // Save the selected filters to storage after applying
// selectedFiltersStorage = List.from(selectedFilters);
// },
// child: Text(context.translate(
// 'Apply Filter',
// 'تطبيق الفلتر',
// )),
// style: ElevatedButton.styleFrom(
// backgroundColor: Colors.blue,
// ),
// ),
// ],
// ),
// ),
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(originalTabChartsData);
cardData = List.from(originalTabCardData);
});
// Reset the selectedFiltersStorage to empty when clearing
selectedFiltersStorage.clear();
Navigator.pop(context);
},
child: Text(context.translate(
'Clear',
'واضح',
)),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey,
Expanded(
child: ElevatedButton(
onPressed: () {
selectedFilters.forEach((filter) {
filter["filter_data"].clear();
});
setState(() {
chartsData = List.from(originalTabChartsData);
cardData = List.from(originalTabCardData);
});
selectedFiltersStorage.clear();
Navigator.pop(context);
},
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12), // Added padding
child: Text(
context.translate('Clear', 'واضح'),
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
overflow:
TextOverflow.ellipsis, // Prevents wrapping
maxLines: 1, // Ensures single line
),
),
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF92722A),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
ElevatedButton(
onPressed: () {
print(
"Selected Filters before applying: $selectedFilters");
setState(() {
chartsData = originalTabChartsData;
cardData = originalTabCardData;
});
SizedBox(width: 16),
Expanded(
child: ElevatedButton(
onPressed: () {
print(
"Selected Filters before applying: $selectedFilters");
setState(() {
chartsData = originalTabChartsData;
cardData = originalTabCardData;
});
applyFilters(context, filters, chartsData, cardData,
selectedFilters);
// Save the selected filters to storage after applying
selectedFiltersStorage = List.from(selectedFilters);
},
child: Text(context.translate(
'Apply Filter',
'تطبيق الفلتر',
)),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
applyFilters(context, filters, chartsData, cardData,
selectedFilters);
selectedFiltersStorage = List.from(selectedFilters);
},
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12), // Added padding
child: FittedBox(
fit: BoxFit
.scaleDown, // Ensures text resizes if necessary
child: Text(
context.translate(
'Apply Filter', 'تطبيق الفلتر'),
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
overflow:
TextOverflow.ellipsis, // Prevents wrapping
maxLines: 1, // Ensures single line
),
),
),
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF92722A),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
],
@ -1235,9 +1323,18 @@ class _ChartScreen1State extends ConsumerState<ChartScreen1> {
@override
Widget build(BuildContext context) {
final locale = ref.watch(localeProvider);
final localeNotifier = ref.read(localeProvider.notifier);
ref.listen<Locale?>(localeProvider, (previous, next) {
final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null
fetchChartData(widget.dataSets, localeCode);
// fetchChartData(widget.dataSets, localeCode);
fetchChartData(widget.dataSets, localeCode).then((_) {
if (_tabsData.isNotEmpty) {
// Call onTabSelected for the first tab
onTabSelected(_tabsData[0]['id']);
}
});
});
double myheight = MediaQuery.of(context).size.height;
double mywidth = MediaQuery.of(context).size.width;
@ -1249,6 +1346,23 @@ class _ChartScreen1State extends ConsumerState<ChartScreen1> {
appBar: AppBar(
backgroundColor: color,
elevation: 0,
actions: [
// IconButton(
// onPressed: () {},
// icon: const Icon(Icons.toggle_off_outlined),
// ),
MyToggle(
// key: toggleKey,
isOn: locale?.languageCode == 'en',
knobTextWhenOn: 'ع',
knobTextWhenOff: 'EN',
pathColorWhenOn: Colors.grey.shade300,
pathColorWhenOff: Colors.grey.shade300,
onTap: () {
ref.read(localeProvider.notifier).toggleLocale();
},
),
],
leading: IconButton(
icon: Icon(Icons.arrow_back_ios_new, color: Colors.white),
onPressed: () {
@ -1343,8 +1457,12 @@ class _ChartScreen1State extends ConsumerState<ChartScreen1> {
),
),
SizedBox(width: 5),
Icon(Icons.bookmarks_outlined,
color: Colors.white, size: 18),
Image.asset(
UaeNumbersAssetPath.bookmarksUae,
color: Colors.white,
width: 24,
height: 24,
),
],
),
SizedBox(width: 10),
@ -1361,8 +1479,12 @@ class _ChartScreen1State extends ConsumerState<ChartScreen1> {
),
),
SizedBox(width: 5),
Icon(Icons.share_sharp,
color: Colors.white, size: 18),
Image.asset(
UaeNumbersAssetPath.shareUae,
color: Colors.white,
width: 24,
height: 24,
),
],
),
SizedBox(
@ -1492,10 +1614,13 @@ class _ChartScreen1State extends ConsumerState<ChartScreen1> {
Flexible(
fit: FlexFit.loose,
child: FittedBox(
// fit: BoxFit.contain,
child: Text(
'${chart_heading ?? 'NA'}',
textAlign: TextAlign.center,
maxLines: 2, // Limit to 2 lines
softWrap:
true, // Enable soft wrapping
// overflow: TextOverflow.ellipsis, // Handle overflow gracefully
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
@ -1584,19 +1709,28 @@ class _ChartScreen1State extends ConsumerState<ChartScreen1> {
},
),
const SizedBox(height: 5),
Text(
'${chart_heading ?? 'NA'}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: Colors.black87,
Flexible(
fit: FlexFit.loose,
child: FittedBox(
child: Text(
'${chart_heading ?? 'NA'}',
textAlign: TextAlign.center,
maxLines: 2, // Limit to 2 lines
softWrap:
true, // Enable soft wrapping
// overflow: TextOverflow.ellipsis, // Handle overflow gracefully
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: Colors.black87,
),
),
),
),
Text(
'(${data['firstYear'] ?? 'NA'} - ${data['lastYear'] ?? 'NA'})',
style: const TextStyle(
fontSize: 11, color: Colors.grey),
fontSize: 10, color: Colors.grey),
),
Flexible(
fit: FlexFit.loose,
@ -1640,10 +1774,13 @@ class _ChartScreen1State extends ConsumerState<ChartScreen1> {
Flexible(
fit: FlexFit.loose,
child: FittedBox(
// fit: BoxFit.contain,
child: Text(
'${chart_heading ?? 'NA'}',
textAlign: TextAlign.center,
maxLines: 2, // Limit to 2 lines
softWrap:
true, // Enable soft wrapping
// overflow: TextOverflow.ellipsis, // Handle overflow gracefully
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
@ -1715,14 +1852,17 @@ class _ChartScreen1State extends ConsumerState<ChartScreen1> {
Flexible(
fit: FlexFit.loose,
child: FittedBox(
fit: BoxFit.contain,
child: Text(
apiService.formatAmount(
data['lastYearValue']),
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.w900,
color: Color(0xFF90B0D5),
'${chart_heading ?? 'NA'}',
textAlign: TextAlign.center,
maxLines: 2, // Limit to 2 lines
softWrap:
true, // Enable soft wrapping
// overflow: TextOverflow.ellipsis, // Handle overflow gracefully
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: Colors.black87,
),
),
),

View File

@ -72,27 +72,34 @@ class ChartWidget extends StatelessWidget {
}).toList();
}
List<Widget> generateIndicators(dynamic chartData, groupByValue) {
List<Widget> generateIndicators(dynamic chartData, String groupByValue) {
return chartData['response'].asMap().entries.map<Widget>((entry) {
int index = entry.key;
var data = entry.value;
Color color = Colors.primaries[index % Colors.primaries.length];
String title = data['ObsKey'][groupByValue] ?? '';
String shortTitle =
title.length > 10 ? '${title.substring(0, 10)}' : title;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 16,
height: 16,
width: 10,
height: 10,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
),
SizedBox(width: 8),
Text(
title,
style: TextStyle(fontSize: 14),
Tooltip(
message: title, // Full text on hover
child: Text(
shortTitle,
style: TextStyle(fontSize: 12),
overflow: TextOverflow.ellipsis, // Ensures text doesn't overflow
),
),
],
);
@ -299,7 +306,7 @@ class ChartWidget extends StatelessWidget {
touchTooltipData: BarTouchTooltipData(
// tooltipBgColor: Colors.black.withOpacity(0.8),
fitInsideHorizontally: true,
fitInsideVertically: false,
fitInsideVertically: true,
tooltipPadding: const EdgeInsets.all(8),
tooltipMargin: 16,
getTooltipItem:
@ -541,11 +548,21 @@ class ChartWidget extends StatelessWidget {
),
),
]);
case 'line_trend_2':
case 'line_trend_population':
final List<Color> uniqueColorsLine_trend_2 = [
Color(0xFF6097CD),
Color(0xFFD086A7),
Color(0xFF98BCE5),
Color(0xFFA7B5C5),
Color(0xFFBED3EC),
Color(0xFFD4E3F4),
];
Set<int> selectedYears = {1970, 1980, 1990, 2000, 2010, 2020};
Map<String, Color> groupColorMap = {};
int colorIndex = 0;
for (String group in groupByValues) {
groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length];
groupColorMap[group] = uniqueColorsLine_trend_2[
colorIndex % uniqueColorsLine_trend_2.length];
colorIndex++;
}
@ -565,15 +582,17 @@ class ChartWidget extends StatelessWidget {
);
}
// Find the maximum year and calculate the range for the last 5 years
int maxYear = years.reduce((a, b) => a > b ? a : b);
int minYear = maxYear - 5;
if (years.isNotEmpty) {
// Find the latest available year
int latestYear = years.reduce((a, b) => a > b ? a : b);
selectedYears.add(latestYear); // Include the latest year in selection
}
// Filter chart data to only include entries within the last 5 years
// Filter chart data to include only the selected years
List<dynamic> filteredData =
(chartData['response'] as List<dynamic>).where((entry) {
int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0;
return year >= minYear && year <= maxYear;
return selectedYears.contains(year);
}).toList();
Set<double> uniqueXValues = filteredData
@ -583,7 +602,7 @@ class ChartWidget extends StatelessWidget {
// Generate line bars for the chart
List<LineChartBarData> lineBars =
lineBarsData2(filteredData, groupByValues, groupByKey);
lineBarsData(filteredData, groupByValues, groupByKey);
return Column(children: [
Text(
@ -600,12 +619,124 @@ class ChartWidget extends StatelessWidget {
child: LineChart(LineChartData(
lineTouchData: lineTouchData1(),
gridData: gridData(),
titlesData: titlesData2(uniqueXValues),
titlesData: titlesData1(uniqueXValues),
borderData: borderData(),
lineBarsData: lineBars,
minX: uniqueXValues.reduce((a, b) => a < b ? a : b),
maxX: uniqueXValues.reduce((a, b) => a > b ? a : b),
))),
Padding(
padding: const EdgeInsets.all(8.0),
child: Wrap(
spacing: 12,
runSpacing: 8,
children: groupByValues.map((group) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 12,
height: 12,
color: groupColorMap[group],
),
SizedBox(width: 6),
Text(
group,
style: TextStyle(fontSize: 14),
),
],
);
}).toList(),
),
),
]);
case 'line_trend_2':
final List<Color> uniqueColorsLine_trend_2 = [
Color(0xFF6097CD),
Color(0xFFD086A7),
Color(0xFF98BCE5),
Color(0xFFA7B5C5),
Color(0xFFBED3EC),
Color(0xFFD4E3F4),
];
Set<int> selectedYears = {1970, 1980, 1990, 2000, 2010, 2020};
Map<String, Color> groupColorMap = {};
int colorIndex = 0;
for (String group in groupByValues) {
groupColorMap[group] = uniqueColorsLine_trend_2[
colorIndex % uniqueColorsLine_trend_2.length];
colorIndex++;
}
// Extract all years from the chart data
List<int> years = (chartData['response'] as List<dynamic>)
.map<int>((entry) =>
int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0)
.toList();
if (years.isEmpty) {
// Return an empty chart if no data
return LineChart(
LineChartData(
titlesData: FlTitlesData(show: false),
lineBarsData: [],
),
);
}
if (years.isNotEmpty) {
// Find the latest available year
int latestYear = years.reduce((a, b) => a > b ? a : b);
selectedYears.add(latestYear); // Include the latest year in selection
}
// Filter chart data to include only the selected years
List<dynamic> filteredData =
(chartData['response'] as List<dynamic>).where((entry) {
int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0;
return selectedYears.contains(year);
}).toList();
Set<double> uniqueXValues = filteredData
.map<double>(
(entry) => double.parse(entry['ObsKey']['TIME_PERIOD']))
.toSet();
// Generate line bars for the chart
List<LineChartBarData> lineBars = lineBarsData2(filteredData);
return Column(children: [
Text(
chartData['chart_heading'] ?? '', // Chart title from data
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
),
SizedBox(height: 10),
// Chart
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal, // Enable horizontal scrolling
padding: const EdgeInsets.only(right: 40),
child: SizedBox(
width: (uniqueXValues.length * 80) +
80, // Adjust width dynamically
child: LineChart(
LineChartData(
lineTouchData: lineTouchData1(),
gridData: gridData(),
titlesData: titlesData1(uniqueXValues),
borderData: borderData(),
lineBarsData: lineBars,
minX: uniqueXValues.reduce((a, b) => a < b ? a : b),
maxX: uniqueXValues.reduce((a, b) => a > b ? a : b),
),
),
),
),
),
]);
case 'bar_chart':
// Extract groupBy values and their corresponding y-axis values
@ -697,6 +828,106 @@ class ChartWidget extends StatelessWidget {
),
))
]);
case 'bar_chart_horizontal':
// Extract groupBy values and their corresponding y-axis values
List<String> xAxisData = [];
List<double> yAxisData = [];
for (var entry in chartData['response']) {
var xValue = entry['ObsKey'][groupByKey];
var yValue = entry['ObsValue']['Value'];
if (xValue != null && yValue != null) {
xAxisData.add(xValue.toString());
yAxisData.add(double.tryParse(yValue.toString()) ?? 0.0);
}
}
int rotationTurns = 1;
return Column(children: [
Text(
chartData['chart_heading'] ?? '', // Chart title from data
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
),
SizedBox(height: 10),
// Chart
Expanded(
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
maxY: yAxisData.isNotEmpty
? yAxisData.reduce((a, b) => a > b ? a : b) * 1.2
: 10,
rotationQuarterTurns: rotationTurns,
barTouchData: BarTouchData(enabled: true),
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: false,
interval: (yAxisData.isNotEmpty
? yAxisData.reduce((a, b) => a > b ? a : b) / 5
: 1),
getTitlesWidget: (value, meta) {
return Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Text('${value.toInt()}'),
);
},
reservedSize: 60,
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
if (value.toInt() < xAxisData.length) {
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Transform.rotate(
angle: -45 *
(3.1415927 / 180), // Rotating by -45 degrees
alignment: Alignment.center,
child: Text(
xAxisData[value.toInt()],
style: TextStyle(fontSize: 12),
),
),
);
}
return Container();
},
reservedSize: 60,
),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false), // Hide top titles
),
rightTitles: AxisTitles(
sideTitles:
SideTitles(showTitles: false), // Hide right titles
),
),
gridData: FlGridData(show: false),
borderData: FlBorderData(show: false),
barGroups: List.generate(
xAxisData.length,
(index) => BarChartGroupData(
x: index,
barRods: [
BarChartRodData(
toY: yAxisData[index],
color: Colors.blueAccent,
borderRadius: BorderRadius.circular(4),
width: 20,
),
],
),
),
),
))
]);
case 'fl_multi_bar':
double _calculateChartWidth(dynamic chartData) {
int totalBars = chartData['response']?.length ?? 0;
@ -712,6 +943,15 @@ class ChartWidget extends StatelessWidget {
if (chartData['dataset'] == 'health_services') {
crop = item['ObsKey']['SECTOR'];
cropType = item['ObsKey'][groupByKey];
} else if (chartData['dataset'] == 'general_education') {
crop = item['ObsKey']['GENDER'];
cropType = item['ObsKey'][groupByKey];
} else if (chartData['dataset'] == 'higher_education') {
crop = item['ObsKey']['GENDER'];
cropType = item['ObsKey'][groupByKey];
} else if (chartData['dataset'] == 'labour_force') {
crop = item['ObsKey']['GENDER'];
cropType = item['ObsKey'][groupByKey];
} else {
crop = item['ObsKey']['CROP'];
cropType = item['ObsKey'][groupByKey];
@ -781,6 +1021,12 @@ class ChartWidget extends StatelessWidget {
barTouchData: BarTouchData(
touchTooltipData: BarTouchTooltipData(
tooltipHorizontalAlignment: FLHorizontalAlignment.center,
tooltipRoundedRadius: 8,
fitInsideHorizontally:
true, // Ensure it fits within the screen
fitInsideVertically: true,
tooltipPadding: EdgeInsets.all(8),
tooltipMargin: 16,
// Only show tooltip when touched
getTooltipItem: (group, groupIndex, rod, rodIndex) {
if (rod.toY == 0 || touchedGroupIndex == -1) {
@ -873,8 +1119,14 @@ class ChartWidget extends StatelessWidget {
// Iterate over the chartData to group crops by CROP_TYPE
for (var item in chartData['response']) {
String crop = item['ObsKey']['CROP'];
String cropType = item['ObsKey']['CROP_TYPE'];
String crop, cropType;
if (chartData['dataset'] == 'natural_reserves') {
crop = item['ObsKey']['NR_TYPE'];
cropType = item['ObsKey'][groupByKey];
} else {
crop = item['ObsKey']['CROP'];
cropType = item['ObsKey'][groupByKey];
}
if (groupedCrops.containsKey(cropType)) {
groupedCrops[cropType]!.add(crop);
@ -985,15 +1237,22 @@ class ChartWidget extends StatelessWidget {
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 20, // Added space for rotated titles
reservedSize: 80, // Added space for rotated titles
getTitlesWidget: (value, meta) {
if (value < groupByValues.length) {
String title = groupByValues.elementAt(value.toInt());
return Transform.rotate(
angle: -0.5, // Rotation in radians (~ -30 degrees)
child: Text(
title,
style: const TextStyle(fontSize: 12),
angle: -1.58, // Rotation in radians (~ -30 degrees)
child: Center(
child: SizedBox(
width: 80,
child: Text(
title,
style: const TextStyle(fontSize: 12),
softWrap: true,
maxLines: 2,
),
),
),
);
}
@ -1010,7 +1269,7 @@ class ChartWidget extends StatelessWidget {
show: true,
border: const Border(
// left: BorderSide(color: Colors.grey),
bottom: BorderSide(color: Colors.grey),
bottom: BorderSide(color: Colors.white),
),
),
gridData: FlGridData(
@ -1084,47 +1343,25 @@ class ChartWidget extends StatelessWidget {
);
}
List<LineChartBarData> lineBarsData2(List<dynamic> filteredData,
Set<String> groupByValues, String groupByKey) {
List<LineChartBarData> lineBarsData2(List<dynamic> filteredData) {
List<LineChartBarData> lineBars = [];
print('filteredData $filteredData');
print('filteredData222 $groupByValues');
final List<Color> uniqueColors = [
Color(0xFF648CBA),
Color(0xFF90B0D5),
Color(0xFF98BCE5),
Color(0xFFA7B5C5),
Color(0xFFBED3EC),
Color(0xFFD4E3F4),
];
// Create a map to assign colors to each group in order
Map<String, Color> groupColorMap = {};
int colorIndex = 0;
for (String group in groupByValues) {
groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length];
colorIndex++;
}
print('Group-Color Map: $groupColorMap');
// Create a map to store the values by year and gender (M and F)
// Create a map to store population values by year and gender
Map<int, Map<String, double>> yearGenderMap = {};
// Populate the yearGenderMap with male and female values
for (var entry in filteredData) {
int year = int.parse(entry['ObsKey']['TIME_PERIOD']);
int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'].toString()) ?? 0;
String gender = entry['ObsKey']['GENDER'];
double value = double.parse(entry['ObsValue']['Value'].toString());
double value =
double.tryParse(entry['ObsValue']['Value'].toString()) ?? 0.0;
if (!yearGenderMap.containsKey(year)) {
yearGenderMap[year] = {'M': 0.0, 'F': 0.0};
}
// Assign value based on gender
if (gender == 'M') {
if (gender == 'Male') {
yearGenderMap[year]!['M'] = value;
} else if (gender == 'F') {
} else if (gender == 'Female') {
yearGenderMap[year]!['F'] = value;
}
}
@ -1205,7 +1442,14 @@ class ChartWidget extends StatelessWidget {
// Helper functions for chart styles
LineTouchData lineTouchData1() {
return LineTouchData(
touchTooltipData: LineTouchTooltipData(),
touchTooltipData: LineTouchTooltipData(
// tooltipBgColor: Colors.black.withOpacity(0.7), // Tooltip background
tooltipRoundedRadius: 8,
fitInsideHorizontally: true, // Ensure it fits within the screen
fitInsideVertically: true,
tooltipPadding: EdgeInsets.all(8),
tooltipMargin: 16, // Adds margin to prevent clipping
),
handleBuiltInTouches: true,
);
}
@ -1534,6 +1778,12 @@ class ChartWidget extends StatelessWidget {
toY: value, // Use the parsed value
color: barColor, // Dynamic color
width: 20,
// backDrawRodData: BackgroundBarChartRodData(
// show: true,
// toY: 400000,
// color: Colors.grey.shade300,
// ),
//
);
}).toList();

View File

@ -451,7 +451,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
controller: _usernameController,
focusNode: _focusNodes[0],
decoration: InputDecoration(
hintText: _showHints[0] ? 'Enter the User Name' : null,
// hintText: _showHints[0] ? 'Enter the User Name' : null,
hintStyle: TextStyle(color: Colors.grey),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
@ -477,8 +477,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
controller: _emailController,
focusNode: _focusNodes[1],
decoration: InputDecoration(
hintText:
_showHints[1] ? 'mohammad.hassan@fcsc.gov.ae' : null,
// hintText:
// _showHints[1] ? 'mohammad.hassan@fcsc.gov.ae' : null,
hintStyle: TextStyle(color: Colors.grey),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),

View File

@ -354,14 +354,22 @@ class LoginRoute extends HookConsumerWidget {
'Email',
'بريد إلكتروني',
),
validator: FieldValidator.email(),
validator: (value) {
if (value == null || value.isEmpty) {
return context.translate('Required', 'مطلوب');
}
return FieldValidator.email()(value);
},
imgPath: MiscIconAssetPath.person,
controller: emailCtl,
),
15.verticalSpace,
ThemedFormField(
validator: (text) {
if (text!.length < 8) {
if (text == null || text.isEmpty) {
return context.translate('Required', 'مطلوب');
}
else if (text.length < 8) {
return 'The password must be at least 8 characters';
}
return FieldValidator.password(minLength: 8)(text);
@ -462,16 +470,16 @@ class LoginRoute extends HookConsumerWidget {
width: double.infinity,
child: ElevatedButton(
onPressed: () async {
final prefs = await SharedPreferences.getInstance();
prefs.clear();
final userId = 'guest';
if (userId.isNotEmpty) {
await saveUserId(userId);
}
context.go('/myhomepage');
//context.go('/${context.language}/${BottomNavBarItem.home.routePath}'),
},
//context.go('/${context.language}/${BottomNavBarItem.home.routePath}'),
final prefs = await SharedPreferences.getInstance();
prefs.clear();
final userId = 'guest';
if (userId.isNotEmpty) {
await saveUserId(userId);
}
context.go('/myhomepage');
//context.go('/${context.language}/${BottomNavBarItem.home.routePath}'),
},
//context.go('/${context.language}/${BottomNavBarItem.home.routePath}'),
style: ButtonStyle(
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(
@ -515,7 +523,7 @@ class LoginRoute extends HookConsumerWidget {
);
final fcscBanner = Image.asset(
BannerAssetPath.fcsc,
height: 56,
height: 40,
);
final screenWidth = MediaQuery.of(context).size.width;
final listViewHorizontalPadding =
@ -528,14 +536,16 @@ class LoginRoute extends HookConsumerWidget {
36.verticalSpace,
Align(
alignment: AlignmentDirectional.topEnd,
child: MyToggle(isOn: locale?.languageCode == 'en',
child: MyToggle(
isOn: locale?.languageCode == 'en',
knobTextWhenOn: 'ع',
knobTextWhenOff: 'EN',
pathColorWhenOn: Colors.grey.shade300,
pathColorWhenOff: Colors.grey.shade300,
onTap: (){
onTap: () {
ref.read(localeProvider.notifier).toggleLocale();
},),
},
),
),
16.verticalSpace,
helloAndPleaseLoginTexts,
@ -543,9 +553,9 @@ class LoginRoute extends HookConsumerWidget {
form,
20.verticalSpace,
dontHaveAnAccountRegisterBtn,
36.verticalSpace,
20.verticalSpace,
continueAsGuestBtn,
72.verticalSpace,
25.verticalSpace,
fcscBanner,
],
);

View File

@ -753,14 +753,31 @@ class _BaseScaffoldState extends ConsumerState<BaseScaffold> {
),
ListTile(
leading: Icon(Icons.book),
title: const Text('User Guide'),
// title: const Text('User Guide'),
title: Text(AppLocalizations.of(context)!.guide_title),
onTap: () => context.go('/user-guide'),
),
ListTile(
leading: Icon(Icons.logout),
title: const Text('Logout'),
onTap: () => logout(),
),
if (userId != 'guest')
ListTile(
leading: Icon(Icons.logout),
// title: const Text('Logout'),
title: Text(AppLocalizations.of(context)!.logout),
onTap: () => logout(),
),
if (userId == 'guest')
ListTile(
leading: Icon(Icons.login),
// title: const Text('Login'),
title: Text(AppLocalizations.of(context)!.login_title),
onTap: () => context.go('/login'),
),
if (userId == 'guest')
ListTile(
leading: Icon(Icons.app_registration),
// title: const Text('Register'),
title: Text(AppLocalizations.of(context)!.register_title),
onTap: () => context.go('/register'),
),
],
),
),

View File

@ -2,7 +2,7 @@ name: uae_stat
description: "View statistics about the UAE from the Federal Center for Statistics and Competitiveness."
publish_to: "none"
#version: 0.5.10
version: 1.0.2+3
version: 1.0.5+6
environment:
sdk: ">=3.2.3 <4.0.0"
@ -115,6 +115,8 @@ flutter:
- assets/edit_profile/
- assets/splash_screen/
- assets/app_tour/
- assets/icons/uae_numbers/bookmarks.png
- assets/icons/uae_numbers/share.png
fonts:
- family: Segoe