226 lines
7.4 KiB
Dart
226 lines
7.4 KiB
Dart
import 'dart:developer';
|
|
|
|
import 'package:absensi_qr/constant/app_color.dart';
|
|
import 'package:absensi_qr/constant/app_font_style.dart';
|
|
import 'package:absensi_qr/constant/asset_constant.dart';
|
|
import 'package:absensi_qr/constant/spacing_size.dart';
|
|
import 'package:absensi_qr/domain/enum/notification_type_enum.dart';
|
|
import 'package:absensi_qr/feature_student/navigation/presentation/navigation_controller.dart';
|
|
import 'package:absensi_qr/feature_student/notification/presentation/notification_student_controller.dart';
|
|
import 'package:absensi_qr/models/notification_model.dart';
|
|
import 'package:absensi_qr/utils/app_util.dart';
|
|
import 'package:absensi_qr/features/widgets/textfield_input_widget.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
|
|
class NotificationStudentPage extends StatelessWidget {
|
|
const NotificationStudentPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final controller = Get.find<NotificationStudentController>();
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
backgroundColor: AppColor.backgroundColor,
|
|
elevation: 0,
|
|
centerTitle: false,
|
|
title: Text(
|
|
'Notifikasi',
|
|
style: AppFontStyle.titleText.copyWith(color: Colors.black),
|
|
),
|
|
titleSpacing: 0,
|
|
leading: IconButton(
|
|
icon: Icon(Icons.chevron_left, color: Colors.black, size: 30),
|
|
onPressed: () {
|
|
Get.back();
|
|
},
|
|
),
|
|
),
|
|
body: RefreshIndicator(
|
|
onRefresh: () async {
|
|
await controller.fetchNotifications();
|
|
},
|
|
child: SingleChildScrollView(
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// each day, show the description like (hari ini, kemarin, 2 hari yang lalu, dst)
|
|
Obx(() => Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: controller.isLoading.value
|
|
? [
|
|
SpacingSize.spacingHugeHeight,
|
|
Container(
|
|
width: double.infinity,
|
|
height: 300,
|
|
alignment: Alignment.center,
|
|
child: CircularProgressIndicator(),
|
|
)
|
|
]
|
|
: _buildNotificationSections(
|
|
context, controller.dataNotifications),
|
|
)),
|
|
|
|
SpacingSize.spacingHugeHeight,
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
List<Widget> _buildNotificationSections(
|
|
BuildContext context,
|
|
List<NotificationModel> notifications,
|
|
) {
|
|
final Map<DateTime, List<NotificationModel>> groupedNotifications = {};
|
|
|
|
for (final notification in notifications) {
|
|
final createdAtNotification = notification.createdAt;
|
|
if (createdAtNotification == null) {
|
|
log('Notification with null createdAt: ${notification.title}');
|
|
continue;
|
|
}
|
|
|
|
final DateTime dayKey = DateTime(
|
|
createdAtNotification.year,
|
|
createdAtNotification.month,
|
|
createdAtNotification.day,
|
|
);
|
|
|
|
groupedNotifications.putIfAbsent(dayKey, () => []);
|
|
groupedNotifications[dayKey]!.add(notification);
|
|
}
|
|
|
|
final List<DateTime> sortedDays = groupedNotifications.keys.toList()
|
|
..sort((a, b) => b.compareTo(a));
|
|
|
|
return sortedDays
|
|
.map(
|
|
(day) => Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
_formatDayLabel(day),
|
|
style: AppFontStyle.primaryText.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
SpacingSize.spacingBaseHeight,
|
|
...groupedNotifications[day]!
|
|
.map(
|
|
(notification) => _cardNotification(
|
|
notification.title,
|
|
notification.body,
|
|
notification.createdAt!,
|
|
notification.type,
|
|
),
|
|
)
|
|
.toList(),
|
|
],
|
|
),
|
|
)
|
|
.toList();
|
|
}
|
|
|
|
String _formatDayLabel(DateTime dateTime) {
|
|
final DateTime today = DateTime.now();
|
|
final DateTime currentDay = DateTime(today.year, today.month, today.day);
|
|
final DateTime targetDay =
|
|
DateTime(dateTime.year, dateTime.month, dateTime.day);
|
|
final int diffDays = currentDay.difference(targetDay).inDays;
|
|
|
|
if (diffDays == 0) {
|
|
return 'Hari ini';
|
|
}
|
|
|
|
if (diffDays == 1) {
|
|
return 'Kemarin';
|
|
}
|
|
|
|
return AppUtil.formatDateIndonesia(dateTime);
|
|
}
|
|
|
|
Widget _cardNotification(String title, String message, DateTime dateTime,
|
|
NotificationTypeEnum type) {
|
|
final iconPath = AssetConstant.getNotificationIconByType(type.dbValue);
|
|
|
|
return GestureDetector(
|
|
onTap: () {
|
|
final int? navIndex = _mapTypeToNavIndex(type);
|
|
Get.back();
|
|
if (navIndex != null) {
|
|
Get.find<NavigationController>().changePage(navIndex);
|
|
}
|
|
},
|
|
child: Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(12),
|
|
margin: const EdgeInsets.only(bottom: 8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
// map type to icon
|
|
Image.asset(
|
|
iconPath,
|
|
width: 24,
|
|
height: 24,
|
|
errorBuilder: (context, error, stackTrace) {
|
|
log('Error loading icon for type ${type.dbValue}: $error');
|
|
return Icon(Icons.notifications,
|
|
color: AppColor.primaryColor, size: 24);
|
|
},
|
|
),
|
|
SpacingSize.spacingMDWidth,
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(title,
|
|
style: AppFontStyle.primaryText
|
|
.copyWith(fontWeight: FontWeight.bold)),
|
|
SpacingSize.spacingXSHeight,
|
|
Text(message, style: AppFontStyle.subTitleText),
|
|
],
|
|
),
|
|
),
|
|
SpacingSize.spacingMDWidth,
|
|
Text(
|
|
AppUtil.formatTime(dateTime),
|
|
style: AppFontStyle.primaryText
|
|
.copyWith(color: AppColor.colorTextSubtitle),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
int? _mapTypeToNavIndex(NotificationTypeEnum type) {
|
|
switch (type) {
|
|
case NotificationTypeEnum.permission ||
|
|
NotificationTypeEnum.permissionAccepted ||
|
|
NotificationTypeEnum.permissionRejected:
|
|
return 2;
|
|
case NotificationTypeEnum.attendanceViolation:
|
|
return 1;
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
}
|