578 lines
19 KiB
Dart
Executable File
578 lines
19 KiB
Dart
Executable File
import 'dart:convert';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:pocketbase/pocketbase.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:uae_stat/config/api_config.dart';
|
|
import 'package:uae_stat/config/connectivity_provider.dart';
|
|
import 'package:uae_stat/config/theme/service.dart';
|
|
import 'package:uae_stat/config/theme/theme_provider.dart';
|
|
import 'package:uae_stat/config/toggle_lang_service.dart';
|
|
import 'package:uae_stat/domain/use_cases/language.dart';
|
|
import 'package:uae_stat/presentation/components/indicators/locale_provider.dart';
|
|
import '../../custom_drawer_routes.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:uae_stat/l10n/app_localizations.dart';
|
|
|
|
class NotificationPage extends ConsumerStatefulWidget {
|
|
@override
|
|
_NotificationPageState createState() => _NotificationPageState();
|
|
}
|
|
|
|
class _NotificationPageState extends ConsumerState<NotificationPage> {
|
|
// Example notification data
|
|
final _pb = PocketBase(apiUrl);
|
|
bool isLoading = true;
|
|
List<Map<String, dynamic>> notifications = [];
|
|
List<String> userReadedNotiOrNot = [];
|
|
String notificationCount = '';
|
|
|
|
List<dynamic> homePageData = [];
|
|
List<dynamic> filteredData = [];
|
|
|
|
// Current selected tab index
|
|
int selectedTabIndex = 0;
|
|
late final Locale locale;
|
|
dynamic userID;
|
|
late UserService _userService;
|
|
late ThemeMode currentTheme;
|
|
|
|
// Tab categories
|
|
// final List<String> tabs = [
|
|
// "All updates",
|
|
// "App updates",
|
|
// "Social",
|
|
// "Economy",
|
|
// "Environment",
|
|
// "Favourites"
|
|
// ];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
locale = ref.read(localeProvider) ?? const Locale('en');
|
|
_userService = UserService();
|
|
_initializePage();
|
|
|
|
currentTheme = ref.read(themeProvider);
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
final isDark = currentTheme == ThemeMode.dark ||
|
|
(currentTheme == ThemeMode.system &&
|
|
MediaQuery.of(context).platformBrightness == Brightness.dark);
|
|
|
|
final themeMode = (isDark ? 'dark' : 'light');
|
|
fetchData(locale?.languageCode ?? 'en', themeMode);
|
|
});
|
|
}
|
|
|
|
Future<void> fetchData(locale, themeMode) async {
|
|
const baseUrl = '${apiUrl}api/getUAENumbersData';
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse(baseUrl + '?language=$locale&color_mode=$themeMode'),
|
|
headers: {'APP_SIGNATURE': 'fcsc.gov.ae.X7pL9qZm2A'});
|
|
if (response.statusCode == 200) {
|
|
setState(() {
|
|
homePageData = (json.decode(response.body) as List)
|
|
.map((e) => Map<String, dynamic>.from(e))
|
|
.toList();
|
|
filteredData = List.from(homePageData);
|
|
isLoading = false;
|
|
});
|
|
} else {
|
|
throw Exception('Failed to load data');
|
|
}
|
|
} catch (e) {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _initializePage() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
userReadedNotiOrNot = prefs.getStringList('read_notification_ids') ?? [];
|
|
await fetchNotifications(locale.languageCode);
|
|
_processNotifications(notifications, userReadedNotiOrNot);
|
|
}
|
|
|
|
void _processNotifications(
|
|
List<Map<String, dynamic>> notification, List<String> push) {
|
|
|
|
int count = 0;
|
|
for (var product in notification) {
|
|
if (push.contains(product['id'])) {
|
|
count++;
|
|
}
|
|
}
|
|
if (count > 0) {
|
|
notificationCount = '($count)';
|
|
} else {
|
|
notificationCount = '';
|
|
}
|
|
}
|
|
|
|
|
|
String formatDate(String dateString) {
|
|
try {
|
|
DateTime dateTime =
|
|
DateTime.parse(dateString); // Convert API date to DateTime
|
|
return DateFormat('dd MMM yyyy hh:mm a')
|
|
.format(dateTime); // Format DateTime
|
|
} catch (e) {
|
|
return 'Invalid Date'; // Handle parsing errors
|
|
}
|
|
}
|
|
|
|
Future<void> fetchNotifications(locale) async {
|
|
final baseUrl = apiUrl + 'api/getNotification';
|
|
try {
|
|
final response = await http.get(Uri.parse('$baseUrl?language=$locale'),
|
|
headers: {'APP_SIGNATURE': 'fcsc.gov.ae.X7pL9qZm2A'});
|
|
|
|
if (response.statusCode == 200) {
|
|
final Map<String, dynamic> jsonResponse =
|
|
jsonDecode(response.body); // Decode JSON
|
|
|
|
if (jsonResponse['message'] == 'Success') {
|
|
setState(() {
|
|
notifications = List<Map<String, dynamic>>.from(
|
|
jsonResponse['data']);
|
|
isLoading = false;
|
|
});
|
|
} else {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
throw Exception('Failed to load data');
|
|
}
|
|
} else {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
throw Exception(
|
|
'Failed to load data with status code ${response.statusCode}');
|
|
}
|
|
} catch (e) {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
ref.listen<AsyncValue<bool>>(connectivityProvider, (previous, hasInternet) {
|
|
if (hasInternet.value == false) {
|
|
context.push('/internetcheck');
|
|
}
|
|
});
|
|
|
|
// ref.listen<Locale?>(localeProvider, (previous, next) async {
|
|
// final localeCode = next?.languageCode ?? 'en'; // Default to 'en' if null
|
|
// setState(() {
|
|
// isLoading = true;
|
|
// });
|
|
// await _userService.updateLanguage(localeCode);
|
|
// fetchNotifications(localeCode);
|
|
// });
|
|
|
|
ref.listen<Locale?>(localeProvider, (previous, next) async {
|
|
final localeCode = next?.languageCode ?? 'en';
|
|
setState(() {
|
|
isLoading = true;
|
|
});
|
|
await _userService.updateLanguage(localeCode, ref);
|
|
final currentTheme = ref.read(themeProvider);
|
|
final isDark = currentTheme == ThemeMode.dark ||
|
|
(currentTheme == ThemeMode.system &&
|
|
MediaQuery.of(context).platformBrightness == Brightness.dark);
|
|
|
|
final themeString = isDark ? 'dark' : 'light';
|
|
fetchData(localeCode, themeString);
|
|
await fetchNotifications(localeCode);
|
|
_processNotifications(notifications, userReadedNotiOrNot);
|
|
});
|
|
|
|
ref.listen<ThemeMode>(themeProvider, (previous, next) async {
|
|
final localeCode = ref.watch(localeProvider)?.languageCode ?? 'en';
|
|
|
|
// Default to 'en' if null
|
|
setState(() {
|
|
isLoading = true;
|
|
});
|
|
|
|
final currentTheme = ref.read(themeProvider);
|
|
final isDark = currentTheme == ThemeMode.dark ||
|
|
(currentTheme == ThemeMode.system &&
|
|
MediaQuery.of(context).platformBrightness == Brightness.dark);
|
|
|
|
final themeString = isDark ? 'dark' : 'light';
|
|
|
|
fetchData(localeCode, themeString);
|
|
await fetchNotifications(localeCode);
|
|
_processNotifications(notifications, userReadedNotiOrNot);
|
|
});
|
|
|
|
final themeMode = ref.read(themeProvider);
|
|
final isDarkTheme = themeMode == ThemeMode.dark ||
|
|
(themeMode == ThemeMode.system &&
|
|
MediaQuery.of(context).platformBrightness == Brightness.dark);
|
|
|
|
|
|
final List<Map<String, dynamic>> tabs = [
|
|
{'title': AppLocalizations.of(context)!.all_updates},
|
|
{'title': AppLocalizations.of(context)!.app_updates},
|
|
{'title': AppLocalizations.of(context)!.notificationSocial},
|
|
{'title': AppLocalizations.of(context)!.notificationEconomy},
|
|
{'title': AppLocalizations.of(context)!.notificationEnvironment},
|
|
{'title': AppLocalizations.of(context)!.notificationFavourites},
|
|
];
|
|
|
|
// final List<Map<String, dynamic>> tabs = [
|
|
// {
|
|
// 'title': AppLocalizations.of(context)!.all_updates,
|
|
// 'color': Colors.blue,
|
|
// },
|
|
// {
|
|
// 'title': AppLocalizations.of(context)!.app_updates,
|
|
// 'color': Colors.green,
|
|
// },
|
|
// {
|
|
// 'title': AppLocalizations.of(context)!.notificationSocial,
|
|
// 'color': Color(0xFF675863),
|
|
// },
|
|
// {
|
|
// 'title': AppLocalizations.of(context)!.notificationEconomy,
|
|
// 'color': Color(0xFF89AFD8),
|
|
// },
|
|
// {
|
|
// 'title': AppLocalizations.of(context)!.notificationEnvironment,
|
|
// 'color': Color(0xFFB3CF99),
|
|
// },
|
|
// {
|
|
// 'title': AppLocalizations.of(context)!.notificationFavourites,
|
|
// 'color': Colors.red,
|
|
// },
|
|
// ];
|
|
|
|
// Filter notifications based on the selected tab
|
|
List<Map<String, dynamic>> filteredNotifications = selectedTabIndex == 0
|
|
? notifications
|
|
: notifications.where((notification) {
|
|
// print("Comparing: '${notification["category"]}' with '${tabs[selectedTabIndex]}'");
|
|
return notification["category"] == tabs[selectedTabIndex]['title'];
|
|
}).toList();
|
|
|
|
return PopScope(
|
|
canPop: false,
|
|
onPopInvokedWithResult: (didPop, result) {
|
|
if (didPop) return;
|
|
context.go('/myhomepage');
|
|
},
|
|
child: BaseScaffold(
|
|
backgroundColor: isDarkTheme ? Colors.black : Colors.white,
|
|
appbarColor: isDarkTheme ? Colors.black : Colors.white,
|
|
title: Text(
|
|
context.translate(
|
|
'Notification $notificationCount',
|
|
'$notificationCount إشعار ',
|
|
),
|
|
style: TextStyle(
|
|
fontFamily: context.translate(
|
|
'Roboto',
|
|
'NotoKufi',
|
|
)),
|
|
),
|
|
body: isLoading
|
|
? Container(
|
|
// color: Color(0x98FFFCE5), // Semi-transparent background
|
|
color: isDarkTheme
|
|
? Colors.black
|
|
: Color(0x98FFFCE5), // Semi-transparent background
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Container(
|
|
margin: EdgeInsets.symmetric(
|
|
horizontal: 40), // Left & Right space
|
|
child: LinearProgressIndicator(
|
|
minHeight: 5, // Adjust thickness
|
|
backgroundColor:
|
|
Colors.grey[100], // Optional: Background color
|
|
valueColor: AlwaysStoppedAnimation<Color>(
|
|
Color(0xFFAA8E83)), // Loader color
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: Column(
|
|
children: [
|
|
TabBarHeader(
|
|
tabs: tabs.map((tab) => tab['title'] as String).toList(),
|
|
selectedIndex: selectedTabIndex,
|
|
isDarkTheme: isDarkTheme,
|
|
onTabSelected: (index) {
|
|
setState(() {
|
|
selectedTabIndex = index;
|
|
});
|
|
},
|
|
),
|
|
Expanded(
|
|
child: filteredNotifications.isEmpty
|
|
? Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(Icons.info_outline,
|
|
size: 100, color: Colors.grey[400]),
|
|
SizedBox(height: 16),
|
|
Text(
|
|
'No notifications available.',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: Colors.grey,
|
|
fontFamily: context.translate(
|
|
'Roboto',
|
|
'NotoKufi',
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: ListView.builder(
|
|
itemCount: filteredNotifications.length,
|
|
itemBuilder: (context, index) {
|
|
final notification = filteredNotifications[index];
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
border: Border(
|
|
bottom: BorderSide(
|
|
color: Color(0xFFDEDEDE),
|
|
width: 1), // Bottom border only
|
|
),
|
|
),
|
|
child: NotificationTile(
|
|
title: notification['title'] ?? 'No Title',
|
|
date:
|
|
formatDate(notification['created'] ?? ''),
|
|
category:
|
|
notification['category'] ?? 'Unknown',
|
|
message: notification['message'] ?? '',
|
|
id: notification['id'],
|
|
userReadedNotiOrNot: userReadedNotiOrNot,
|
|
userID: userID,
|
|
isDarkTheme: isDarkTheme,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class TabBarHeader extends StatelessWidget {
|
|
final List<String> tabs;
|
|
final int selectedIndex;
|
|
final ValueChanged<int> onTabSelected;
|
|
bool isDarkTheme;
|
|
|
|
TabBarHeader({
|
|
required this.tabs,
|
|
required this.selectedIndex,
|
|
required this.onTabSelected,
|
|
required this.isDarkTheme,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
color: isDarkTheme ? Colors.black : Colors.white,
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
child: SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
children: tabs.asMap().entries.map((entry) {
|
|
int index = entry.key;
|
|
String text = entry.value;
|
|
return Row(
|
|
children: [
|
|
TextButton(
|
|
onPressed: () => onTabSelected(index),
|
|
style: ButtonStyle(
|
|
foregroundColor:
|
|
MaterialStateProperty.resolveWith((states) {
|
|
return selectedIndex == index
|
|
? isDarkTheme
|
|
? Color(0xFFFFFFFF)
|
|
: Color(0xFFB68A34)
|
|
: isDarkTheme
|
|
? Color(0xFFBBBBBB)
|
|
: Color(0xFF898C81);
|
|
}),
|
|
),
|
|
child: Text(
|
|
text,
|
|
style: TextStyle(
|
|
fontFamily: context.translate(
|
|
'Roboto',
|
|
'NotoKufi',
|
|
),
|
|
),
|
|
),
|
|
),
|
|
if (index < tabs.length - 1) buildDivider(),
|
|
],
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildDivider() {
|
|
return Container(
|
|
height: 20,
|
|
child: const VerticalDivider(
|
|
color: Colors.grey,
|
|
thickness: 1,
|
|
width: 16,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class NotificationTile extends ConsumerWidget {
|
|
final String title;
|
|
final String message;
|
|
final String date;
|
|
final String category;
|
|
final String id;
|
|
final String userID;
|
|
final List<String> userReadedNotiOrNot;
|
|
bool isDarkTheme;
|
|
|
|
NotificationTile({
|
|
required this.title,
|
|
required this.message,
|
|
required this.date,
|
|
required this.category,
|
|
required this.id,
|
|
required this.userID,
|
|
required this.userReadedNotiOrNot,
|
|
required this.isDarkTheme,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
bool isPushed = userReadedNotiOrNot.contains(id);
|
|
|
|
return GestureDetector(
|
|
onTap: () {
|
|
readedNotification(userID, id, ref);
|
|
final encodedKey = Uri.encodeQueryComponent('notification');
|
|
context.go('/notification_details', extra: {
|
|
'title': title,
|
|
'message': message,
|
|
'date': date,
|
|
'category': category,
|
|
'id': id,
|
|
'backNavigation': encodedKey
|
|
});
|
|
},
|
|
child: ListTile(
|
|
// leading: isPushed
|
|
// ? const Icon(Icons.circle, size: 12, color: Color(0xFFFF274E))
|
|
// : Icon(Icons.circle,
|
|
// size: 12,
|
|
// color: isDarkTheme ? Colors.white : const Color(0x99414042)),
|
|
// leading: isPushed
|
|
// ? const Icon(Icons.circle, size: 12, color: Color(0xFFFF274E))
|
|
// : const Icon(Icons.circle, size: 12, color: Colors.transparent),
|
|
title: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: EdgeInsets.only(top: 4.0, right: 6),
|
|
child: isPushed
|
|
? const Icon(Icons.circle, size: 8, color: Color(0xFFFF274E))
|
|
: const Icon(Icons.circle,
|
|
size: 8, color: Colors.transparent),
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
fontFamily: context.translate('Roboto', 'NotoKufi'),
|
|
color: isPushed
|
|
? (isDarkTheme
|
|
? Colors.white
|
|
: Color(0xFF414042)) // dark title
|
|
: (isDarkTheme
|
|
? Color(0xFF999999)
|
|
: Color(0x99414042)), // light grey title
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
// Text(
|
|
// title,
|
|
// style: TextStyle(
|
|
// color: isPushed
|
|
// ? isDarkTheme
|
|
// ? Colors.white
|
|
// : Color(0xFF414042)
|
|
// : isDarkTheme ? Color(0xFF999999) : Color(0xFF999999),
|
|
// fontWeight: FontWeight.bold,
|
|
// fontFamily: context.translate(
|
|
// 'Roboto',
|
|
// 'NotoKufi',
|
|
// ),
|
|
// ),
|
|
// ),
|
|
subtitle: Padding(
|
|
padding: const EdgeInsets.only(
|
|
left: 15), // 👈 set your desired left padding here
|
|
child: Text(
|
|
date,
|
|
style: TextStyle(
|
|
color: isDarkTheme ? Color(0xFF565656) : Color(0xFF8E8E8E),
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w400,
|
|
fontFamily: context.translate(
|
|
'Roboto',
|
|
'NotoKufi',
|
|
),
|
|
),
|
|
),
|
|
),
|
|
trailing: const Icon(Icons.arrow_forward_ios,
|
|
size: 16, color: Color(0xFF898C81)),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> readedNotification(
|
|
String userId, String notificationId, WidgetRef ref) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final read = prefs.getStringList('read_notification_ids') ?? [];
|
|
if (!read.contains(notificationId)) {
|
|
read.add(notificationId);
|
|
await prefs.setStringList('read_notification_ids', read);
|
|
}
|
|
}
|