339 lines
12 KiB
Dart
339 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'package:smpn1_lengkong/core/constants/app_colors.dart';
|
|
import 'package:smpn1_lengkong/core/models/student_model.dart';
|
|
import 'package:smpn1_lengkong/core/routes/app_routes.dart';
|
|
import 'package:smpn1_lengkong/core/service/student_service.dart';
|
|
import 'package:smpn1_lengkong/main.dart';
|
|
|
|
class NotificationScreen extends StatefulWidget {
|
|
const NotificationScreen({super.key});
|
|
|
|
@override
|
|
State<NotificationScreen> createState() => _NotificationScreenState();
|
|
}
|
|
|
|
class _NotificationScreenState extends State<NotificationScreen> {
|
|
late Future<List<Map<String, dynamic>>> _notificationsFuture;
|
|
late StudentModel student;
|
|
bool _isInitialized = false;
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
if (!_isInitialized) {
|
|
student = ModalRoute.of(context)!.settings.arguments as StudentModel;
|
|
_notificationsFuture = StudentService().getNotifications(
|
|
student.id,
|
|
student.className,
|
|
);
|
|
_isInitialized = true;
|
|
}
|
|
}
|
|
|
|
Future<void> _refreshData() async {
|
|
setState(() {
|
|
_notificationsFuture = StudentService().getNotifications(
|
|
student.id,
|
|
student.className,
|
|
);
|
|
});
|
|
await _notificationsFuture;
|
|
}
|
|
|
|
Future<void> _handleReadAll() async {
|
|
await StudentService().markAllAsRead(student.id, student.className);
|
|
await _refreshData();
|
|
if (mounted) {}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: AppColors.backgroundWhite,
|
|
appBar: AppBar(
|
|
title: Text(
|
|
"Pemberitahuan Kelas ${student.className}",
|
|
style: const TextStyle(color: Colors.white),
|
|
),
|
|
backgroundColor: AppColors.primaryBlue,
|
|
foregroundColor: Colors.white,
|
|
elevation: 0,
|
|
),
|
|
body: RefreshIndicator(
|
|
onRefresh: _refreshData,
|
|
color: AppColors.primaryBlue,
|
|
child: FutureBuilder<List<Map<String, dynamic>>>(
|
|
future: _notificationsFuture,
|
|
builder: (context, snapshot) {
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
if (snapshot.hasError) {
|
|
return Center(
|
|
child: Text("Terjadi kesalahan: ${snapshot.error}"),
|
|
);
|
|
}
|
|
|
|
// Ambil data dan lakukan pengurutan (Sorting)
|
|
final List<Map<String, dynamic>> notifications =
|
|
snapshot.data ?? [];
|
|
|
|
if (notifications.isNotEmpty) {
|
|
notifications.sort((a, b) {
|
|
// Parsing tanggal, berikan fallback waktu sekarang jika null/error
|
|
DateTime dateA =
|
|
DateTime.tryParse(a['tanggal']?.toString() ?? '') ??
|
|
DateTime.now();
|
|
DateTime dateB =
|
|
DateTime.tryParse(b['tanggal']?.toString() ?? '') ??
|
|
DateTime.now();
|
|
|
|
// b.compareTo(a) untuk urutan descending (Terbaru -> Terlama)
|
|
return dateB.compareTo(dateA);
|
|
});
|
|
}
|
|
|
|
final int unreadCount =
|
|
notifications.where((n) => n['status'] == 'unread').length;
|
|
|
|
if (notifications.isEmpty) {
|
|
return Stack(
|
|
children: [
|
|
_buildEmptyState(),
|
|
ListView(physics: const AlwaysScrollableScrollPhysics()),
|
|
],
|
|
);
|
|
}
|
|
|
|
return CustomScrollView(
|
|
physics: const AlwaysScrollableScrollPhysics(
|
|
parent: BouncingScrollPhysics(),
|
|
),
|
|
slivers: [
|
|
SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: EdgeInsets.fromLTRB(20.w, 24.h, 20.w, 16.h),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
unreadCount > 0
|
|
? "Anda memiliki $unreadCount pemberitahuan baru"
|
|
: "Semua pemberitahuan sudah diperiksa",
|
|
style: TextStyle(
|
|
fontSize: 13.sp,
|
|
color: AppColors.textSecondary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
if (unreadCount > 0)
|
|
GestureDetector(
|
|
onTap: _handleReadAll,
|
|
child: Container(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 12.w,
|
|
vertical: 8.h,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.primaryBlue.withOpacity(0.08),
|
|
borderRadius: BorderRadius.circular(10.r),
|
|
),
|
|
child: Text(
|
|
"Baca Semua",
|
|
style: TextStyle(
|
|
color: AppColors.primaryBlue,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 12.sp,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
SliverPadding(
|
|
padding: EdgeInsets.symmetric(horizontal: 20.w),
|
|
sliver: SliverList(
|
|
delegate: SliverChildBuilderDelegate((context, index) {
|
|
final item = notifications[index];
|
|
return Padding(
|
|
padding: EdgeInsets.only(bottom: 12.h),
|
|
child: _buildNotificationCard(context, item),
|
|
);
|
|
}, childCount: notifications.length),
|
|
),
|
|
),
|
|
SliverToBoxAdapter(child: SizedBox(height: 30.h)),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildNotificationCard(
|
|
BuildContext context,
|
|
Map<String, dynamic> item,
|
|
) {
|
|
final bool isUnread = item['status'] == 'unread';
|
|
|
|
return InkWell(
|
|
onTap: () async {
|
|
if (isUnread) {
|
|
await StudentService().markNotificationAsRead(item['id']);
|
|
await _refreshData();
|
|
}
|
|
|
|
// Gunakan navigatorKey sebagai pengganti Navigator.pushNamed(context, ...)
|
|
navigatorKey.currentState?.pushNamed(
|
|
AppRoutes.notificationDetail,
|
|
arguments: {
|
|
'title': item['title'] ?? 'Pengumuman',
|
|
'body': item['body'] ?? '',
|
|
'date': item['tanggal'] ?? '',
|
|
},
|
|
);
|
|
},
|
|
borderRadius: BorderRadius.circular(16.r),
|
|
child: Container(
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16.r),
|
|
border: Border.all(
|
|
color:
|
|
isUnread
|
|
? AppColors.primaryBlue.withOpacity(0.3)
|
|
: Colors.grey.shade100,
|
|
width: isUnread ? 1.5 : 1,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.02),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
color:
|
|
isUnread
|
|
? AppColors.primaryBlue.withOpacity(0.1)
|
|
: Colors.grey.shade50,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
child: Icon(
|
|
Icons.notifications_active_outlined,
|
|
color: isUnread ? AppColors.primaryBlue : Colors.grey.shade400,
|
|
size: 24.sp,
|
|
),
|
|
),
|
|
SizedBox(width: 16.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
item['title'] ?? '-',
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: TextStyle(
|
|
fontWeight:
|
|
isUnread ? FontWeight.w800 : FontWeight.w600,
|
|
fontSize: 15.sp,
|
|
color: AppColors.textPrimary,
|
|
),
|
|
),
|
|
),
|
|
if (isUnread)
|
|
Container(
|
|
width: 8.w,
|
|
height: 8.w,
|
|
decoration: const BoxDecoration(
|
|
color: AppColors.dangerRed,
|
|
shape: BoxShape.circle,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 4.h),
|
|
Text(
|
|
item['body'] ?? '-',
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: TextStyle(
|
|
color: isUnread ? Colors.black87 : Colors.grey.shade600,
|
|
fontSize: 13.sp,
|
|
height: 1.5,
|
|
),
|
|
),
|
|
SizedBox(height: 10.h),
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
Icons.access_time_rounded,
|
|
size: 12.sp,
|
|
color: Colors.grey,
|
|
),
|
|
SizedBox(width: 4.w),
|
|
Text(
|
|
item['tanggal']?.toString().length == 16
|
|
? item['tanggal']
|
|
: (item['tanggal']?.toString().substring(0, 16) ??
|
|
''),
|
|
style: TextStyle(
|
|
fontSize: 11.sp,
|
|
color: Colors.grey,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildEmptyState() {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.notifications_none_rounded,
|
|
size: 60.sp,
|
|
color: Colors.grey.shade300,
|
|
),
|
|
SizedBox(height: 16.h),
|
|
Text(
|
|
"Belum Ada Notifikasi",
|
|
style: TextStyle(color: Colors.grey.shade500),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|