diff --git a/android/gradle.properties b/android/gradle.properties index fbee1d8c..d5da7278 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,2 +1,6 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true +# This builtInKotlin flag was added automatically by Flutter migrator +android.builtInKotlin=false +# This newDsl flag was added automatically by Flutter migrator +android.newDsl=false diff --git a/lib/core/constants/notification_type.dart b/lib/core/constants/notification_type.dart new file mode 100644 index 00000000..cc598b38 --- /dev/null +++ b/lib/core/constants/notification_type.dart @@ -0,0 +1,18 @@ +enum NotificationType { + chatMessage('CHAT_MESSAGE'), + matchCreated('MATCH_CREATED'), + postComment('POST_COMMENT'), + rateRequest('RATE_REQUEST'), + postLike('POST_LIKE'), + chatMemberLeft('CHAT_MEMBER_LEFT'), + reportReceived('REPORT_RECEIVED'), + reportResolved('REPORT_RESOLVED'), + reportContentDeleted('REPORT_CONTENT_DELETED'), + userSuspended('USER_SUSPENDED'), + unknown('UNKNOWN') + ; + + final String value; + + const NotificationType(this.value); +} diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index 66360b55..aaa45bcd 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -45,6 +45,7 @@ import 'package:ondo/presentation/post/screens/post_detail_screen.dart'; // notification import 'package:ondo/core/router/bindings/notification_binding.dart'; +import 'package:ondo/presentation/notification/controllers/notification_controller.dart'; import 'package:ondo/presentation/notification/screens/notification_screen.dart'; // profile @@ -196,7 +197,11 @@ final GoRouter appRouter = GoRouter( path: RoutePaths.notification, name: 'notification', builder: (context, state) { - NotificationBinding().dependencies(); + if (Get.isRegistered()) { + Get.find().refresh(); + } else { + NotificationBinding().dependencies(); + } return const NotificationScreen(); }, ), diff --git a/lib/data/models/notification/response/notification_model.dart b/lib/data/models/notification/response/notification_model.dart index f5f40f92..3150ab8e 100644 --- a/lib/data/models/notification/response/notification_model.dart +++ b/lib/data/models/notification/response/notification_model.dart @@ -1,3 +1,4 @@ +import 'package:ondo/core/constants/notification_type.dart'; import 'package:ondo/data/models/base/response/base_model.dart'; import '../../base/response/base_data_model.dart'; @@ -30,7 +31,7 @@ class NotificationDataModel extends BaseDataModel { class NotificationModel extends BaseModel { final int id; - final String type; + final NotificationType type; final String title; final String body; final String target; @@ -49,7 +50,10 @@ class NotificationModel extends BaseModel { factory NotificationModel.fromJson(Map json) => NotificationModel( id: json["id"], - type: json["type"], + type: NotificationType.values.firstWhere( + (t) => t.value == json["type"], + orElse: () => NotificationType.unknown, + ), title: json["title"], body: json["body"], target: json["target"], @@ -60,7 +64,7 @@ class NotificationModel extends BaseModel { @override Map toJson() => { "id": id, - "type": type, + "type": type.value, "title": title, "body": body, "target": target, diff --git a/lib/data/repositories/notification/notification_repository_impl.dart b/lib/data/repositories/notification/notification_repository_impl.dart index a039effb..801a265f 100644 --- a/lib/data/repositories/notification/notification_repository_impl.dart +++ b/lib/data/repositories/notification/notification_repository_impl.dart @@ -2,6 +2,7 @@ import 'package:ondo/data/datasource/notification/notification_local_datasource. import 'package:ondo/data/datasource/notification/notification_remote_datasource.dart'; import 'package:ondo/data/models/base/request/base_list_request_model.dart'; import 'package:ondo/data/models/notification/response/notification_model.dart'; +import 'package:ondo/domain/entities/base/listable_wrapper.dart'; import 'package:ondo/domain/entities/notification/notification_entity.dart'; import 'package:ondo/domain/repositories/notification/notification_repository.dart'; @@ -17,7 +18,7 @@ class NotificationRepositoryImpl extends NotificationRepository { // ─── 서버 알림 ───────────────────────────────────────────────── @override - Future> loadMyNotificationModel( + Future> loadMyNotificationModel( int size, int page, ) async { @@ -28,15 +29,22 @@ class NotificationRepositoryImpl extends NotificationRepository { final json = await remoteDatasource.loadMyNotificationList(model); - if (json == null) return []; + if (json == null) return ListableWrapper.none(); final res = NotificationDataModel.fromJson(json); - return res.content - .map( - (e) => NotificationEntity.fromNotificationModel(e), - ) - .toList(); + return ListableWrapper( + page: res.page, + size: res.size, + totalElements: res.totalElements, + totalPages: res.totalPages, + last: res.last, + content: res.content + .map( + (e) => NotificationEntity.fromNotificationModel(e), + ) + .toList(), + ); } @override diff --git a/lib/domain/entities/notification/notification_entity.dart b/lib/domain/entities/notification/notification_entity.dart index 18bb02fb..944832a1 100644 --- a/lib/domain/entities/notification/notification_entity.dart +++ b/lib/domain/entities/notification/notification_entity.dart @@ -1,9 +1,10 @@ +import 'package:ondo/core/constants/notification_type.dart'; import 'package:ondo/core/utils/app_date_utils.dart'; import 'package:ondo/data/models/notification/response/notification_model.dart'; class NotificationEntity { final int id; - final String type; + final NotificationType type; final String title; final String body; final String target; diff --git a/lib/domain/repositories/notification/notification_repository.dart b/lib/domain/repositories/notification/notification_repository.dart index 122ee9d6..1a0dd627 100644 --- a/lib/domain/repositories/notification/notification_repository.dart +++ b/lib/domain/repositories/notification/notification_repository.dart @@ -1,8 +1,12 @@ +import 'package:ondo/domain/entities/base/listable_wrapper.dart'; import 'package:ondo/domain/entities/notification/notification_entity.dart'; abstract class NotificationRepository { // ─── 서버 알림 ───────────────────────────────────────────────── - Future> loadMyNotificationModel(int size, int page); + Future> loadMyNotificationModel( + int size, + int page, + ); Future loadUnreadNotificationCount(); diff --git a/lib/domain/usecases/notification/load_my_notification_list_use_case.dart b/lib/domain/usecases/notification/load_my_notification_list_use_case.dart index 4c190443..1de28089 100644 --- a/lib/domain/usecases/notification/load_my_notification_list_use_case.dart +++ b/lib/domain/usecases/notification/load_my_notification_list_use_case.dart @@ -1,3 +1,4 @@ +import 'package:ondo/domain/entities/base/listable_wrapper.dart'; import 'package:ondo/domain/entities/notification/notification_entity.dart'; import 'package:ondo/domain/repositories/notification/notification_repository.dart'; @@ -6,10 +7,10 @@ class LoadMyNotificationListUseCase { LoadMyNotificationListUseCase({required this.repository}); - Future> call({ + Future> call({ required int size, required int page, - }) async { - return (await repository.loadMyNotificationModel(size, page)); + }) { + return repository.loadMyNotificationModel(size, page); } } diff --git a/lib/presentation/home/widgets/home_post_rank_item.dart b/lib/presentation/home/widgets/home_post_rank_item.dart index 5c6e19c4..fd1ec89f 100644 --- a/lib/presentation/home/widgets/home_post_rank_item.dart +++ b/lib/presentation/home/widgets/home_post_rank_item.dart @@ -23,6 +23,7 @@ class HomePostRankItem extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( + behavior: HitTestBehavior.opaque, onTap: onTap, child: Row( crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/presentation/notification/controllers/notification_controller.dart b/lib/presentation/notification/controllers/notification_controller.dart index a3773eb4..dfed1153 100644 --- a/lib/presentation/notification/controllers/notification_controller.dart +++ b/lib/presentation/notification/controllers/notification_controller.dart @@ -1,4 +1,6 @@ +import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:go_router/go_router.dart'; import 'package:ondo/core/design_system/components/custom_alert_dialog.dart'; import 'package:ondo/domain/entities/notification/notification_entity.dart'; import 'package:ondo/domain/usecases/notification/delete_all_read_notifications_use_case.dart'; @@ -8,10 +10,19 @@ import 'package:ondo/domain/usecases/notification/read_all_notification_use_case import 'package:ondo/domain/usecases/notification/read_notification_use_case.dart'; class NotificationController extends GetxController { + static const _pageSize = 20; + final RxList viewNotificationList = [].obs; final RxInt newNotificationCount = 0.obs; + final RxBool isLoading = false.obs; + final RxBool isLoadingMore = false.obs; + final RxString errorMessage = ''.obs; + final RxInt totalElements = 0.obs; + + int _currentPage = 0; + bool _isLast = false; final LoadMyNotificationListUseCase loadMyNotificationListUseCase; final LoadUnreadNotificationCountUseCase loadUnreadNotificationCountUseCase; @@ -29,68 +40,125 @@ class NotificationController extends GetxController { @override void onInit() { - _loadMyNotificationList(); - _loadUnreadNotificationCount(); + refresh(); super.onInit(); } - Future _loadMyNotificationList() async { - //TODO : 화면 연동 과정에서 범위 맞추기 - viewNotificationList.assignAll( - await loadMyNotificationListUseCase.call(size: 20, page: 0), - ); + @override + Future refresh() async { + await Future.wait([ + _loadMyNotificationList(), + _loadUnreadNotificationCount(), + ]); } - Future _loadUnreadNotificationCount() async { - newNotificationCount.value = await loadUnreadNotificationCountUseCase - .call(); - } + Future _loadMyNotificationList() async { + _currentPage = 0; + _isLast = false; + errorMessage.value = ''; + isLoading.value = true; - Future _readAllNotification() async { - await readAllNotificationUseCase.call(); + try { + final result = await loadMyNotificationListUseCase.call( + size: _pageSize, + page: 0, + ); + viewNotificationList.assignAll(result.content); + _isLast = result.last ?? true; + totalElements.value = result.totalElements; + _currentPage = 1; + } catch (e) { + debugPrint('[NotificationController] 알림 목록 조회 실패 - error: $e'); + errorMessage.value = '알림을 불러오지 못했습니다.'; + } finally { + isLoading.value = false; + } } - Future _readNotification(int id) async { - return await readNotificationUseCase.call(id); + Future loadMore() async { + if (_isLast || isLoadingMore.value) return; + + isLoadingMore.value = true; + try { + final result = await loadMyNotificationListUseCase.call( + size: _pageSize, + page: _currentPage, + ); + viewNotificationList.addAll(result.content); + _isLast = result.last ?? true; + _currentPage++; + } catch (e) { + debugPrint('[NotificationController] 알림 추가 로드 실패 - error: $e'); + } finally { + isLoadingMore.value = false; + } } - Future _deleteAllReadNotification() async { - return deleteAllReadNotificationsUseCase.call(); + Future _loadUnreadNotificationCount() async { + try { + newNotificationCount.value = await loadUnreadNotificationCountUseCase(); + } catch (e) { + debugPrint('[NotificationController] 읽지 않은 알림 수 조회 실패 - error: $e'); + } } Future read(NotificationEntity notification) async { - if (await _readNotification(notification.id)) { - final index = viewNotificationList.indexWhere( - (e) => e.id == notification.id, - ); - if (index >= 0) { - viewNotificationList[index] = notification.copyWith(read: true); + if (notification.read) return; + + try { + if (await _readNotification(notification.id)) { + final index = viewNotificationList.indexWhere( + (e) => e.id == notification.id, + ); + if (index >= 0) { + viewNotificationList[index] = notification.copyWith(read: true); + } } + } catch (e) { + debugPrint('[NotificationController] 알림 읽음 처리 실패 - error: $e'); } } Future readAll() async { - await _readAllNotification(); - viewNotificationList.assignAll( - viewNotificationList.map((e) => e.copyWith(read: true)), - ); - newNotificationCount.value = 0; + try { + await readAllNotificationUseCase(); + viewNotificationList.assignAll( + viewNotificationList.map((e) => e.copyWith(read: true)).toList(), + ); + newNotificationCount.value = 0; + } catch (e) { + debugPrint('[NotificationController] 전체 읽음 처리 실패 - error: $e'); + } } - Future deleteAllNotification() async => Get.dialog( - CustomAlertDialog( - title: "알림", - comment: "정말 모든 알림을 삭제하시겠어요?", - actionLeft: () => Get.back(), - actionRight: () async { - if (await _deleteAllReadNotification()) { - viewNotificationList.removeWhere( - (notification) => notification.read == true, - ); - } - Get.back(); - }, - rightActionText: "삭제", - ), - ); + Future deleteAllNotification(BuildContext context) async => showDialog( + context: context, + builder: (context) => CustomAlertDialog( + title: "알림", + comment: "정말 모든 알림을 삭제하시겠어요?", + actionLeft: () => context.pop(), + actionRight: () async { + try { + if (await _deleteAllReadNotification()) { + final removedCount = viewNotificationList + .where((n) => n.read == true) + .length; + viewNotificationList.removeWhere((n) => n.read == true); + totalElements.value -= removedCount; + } + } catch (e) { + debugPrint('[NotificationController] 알림 삭제 실패 - error: $e'); + } finally { + if (context.mounted) context.pop(); + } + }, + rightActionText: "삭제", + ), + ); + + Future _readNotification(int id) async => + readNotificationUseCase(id); + + Future _deleteAllReadNotification() async => + deleteAllReadNotificationsUseCase(); } diff --git a/lib/presentation/notification/screens/notification_screen.dart b/lib/presentation/notification/screens/notification_screen.dart index 6eff5f70..83fbdfee 100644 --- a/lib/presentation/notification/screens/notification_screen.dart +++ b/lib/presentation/notification/screens/notification_screen.dart @@ -1,15 +1,13 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:ondo/core/constants/notification_type.dart'; import 'package:ondo/core/design_system/app_colors.dart'; import 'package:ondo/core/design_system/app_icon.dart'; import 'package:ondo/core/design_system/app_layout.dart'; import 'package:ondo/core/design_system/app_text_styles.dart'; import 'package:ondo/core/design_system/components/custom_back_button.dart'; -import 'package:ondo/presentation/notification/controllers/notification_controller.dart'; import 'package:ondo/core/ui/base/base_scaffold.dart'; -import 'package:ondo/presentation/notification/states/notification_state.dart'; +import 'package:ondo/presentation/notification/controllers/notification_controller.dart'; import '../widgets/notification_card.dart'; import '../widgets/report_notification_card.dart'; @@ -31,57 +29,66 @@ class NotificationScreen extends GetView { } Widget _topBar() => CustomBackButton( - moreOptions: true, - itemBuilder: (context) => [ - PopupMenuItem( - padding: AppPadding.popupManuButton, - onTap: controller.deleteAllNotification, - height: double.minPositive, - child: Align( - alignment: Alignment.center, - child: Text( - "읽은 알림 모두 삭제", - style: AppTextStyles.caption(textColor: AppColors.gray90), + moreOptions: true, + itemBuilder: (context) => [ + PopupMenuItem( + padding: AppPadding.popupManuButton, + onTap: () => controller.deleteAllNotification(context), + child: Align( + alignment: Alignment.center, + child: Text( + "읽은 알림 모두 삭제", + style: AppTextStyles.caption(textColor: AppColors.gray90), + ), + ), ), - ), - ), - ], - ); + PopupMenuItem( + padding: AppPadding.popupManuButton, + onTap: controller.readAll, + child: Align( + alignment: Alignment.center, + child: Text( + "전체 알림 모두 읽기", + style: AppTextStyles.caption(textColor: AppColors.gray90), + ), + ), + ), + ], + ); Widget _body() => Container( - color: AppColors.white, - padding: AppPadding.screenHorizontal, - child: Column( - children: [ - _Title(), - AppGap.v16, - Obx( - () => Expanded( - child: controller.viewNotificationList.isNotEmpty - ? _NotificationPageList() - : _noMessageIcon(), - ), - ), - AppGap.v16, - ], - ), - ); + color: AppColors.white, + padding: AppPadding.screenHorizontal, + child: Column( + children: [ + _Title(), + AppGap.v16, + Expanded( + child: Obx(() { + if (controller.isLoading.value && + controller.viewNotificationList.isEmpty) { + return const Center(child: CircularProgressIndicator()); + } - Widget _noMessageIcon() => Column( - children: [ - Spacer( - flex: 153, - ), - Image.asset(AppIcon.message.path), - Text( - "지금은 알려드릴 게 없어요", - style: AppTextStyles.textMedium(textColor: AppColors.gray60), - ), - Spacer( - flex: 275, - ), - ], - ); + if (controller.errorMessage.value.isNotEmpty && + controller.viewNotificationList.isEmpty) { + return _ErrorView( + message: controller.errorMessage.value, + onRetry: controller.refresh, + ); + } + + if (controller.viewNotificationList.isEmpty) { + return _EmptyView(); + } + + return _NotificationList(); + }), + ), + AppGap.v16, + ], + ), + ); } class _Title extends GetView { @@ -100,7 +107,7 @@ class _Title extends GetView { AppGap.h12, Obx( () => Text( - "${controller.viewNotificationList.length}", + "${controller.totalElements.value}", style: AppTextStyles.textMedium(textColor: AppColors.gray60), ), ), @@ -111,69 +118,90 @@ class _Title extends GetView { } } -@immutable -class _NotificationPageList extends GetView { - final ValueNotifier curIndex = ValueNotifier(0); - +class _NotificationList extends GetView { @override Widget build(BuildContext context) { - final list = controller.viewNotificationList; - final pageCount = (list.length / 11).ceil(); - return Column( - children: [ - Expanded( - child: PageView.builder( - itemCount: pageCount, - onPageChanged: (value) => curIndex.value = value, - itemBuilder: (context, pageIndex) { - final slice = list.sublist( - pageIndex * 11, - min((pageIndex + 1) * 11, list.length), + return NotificationListener( + onNotification: (notification) { + if (notification.depth == 0 && + notification.metrics.pixels >= + notification.metrics.maxScrollExtent - 200) { + controller.loadMore(); + } + return false; + }, + child: Obx( + () => ListView.separated( + itemCount: controller.viewNotificationList.length + + (controller.isLoadingMore.value ? 1 : 0), + separatorBuilder: (_, _) => AppGap.v16, + itemBuilder: (context, index) { + if (index == controller.viewNotificationList.length) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 16), + child: Center(child: CircularProgressIndicator()), ); - - return ListView.separated( - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: slice.length, - separatorBuilder: (context, index) => AppGap.v16, - itemBuilder: (context, index) { - final notification = slice[index]; - if (notification.type == NotificationState.reported.title) { - return ReportNotificationCard( - notificationInfo: notification, - ); - } - return NotificationCard( - notificationInfo: notification, + } + final notification = controller.viewNotificationList[index]; + return notification.type == NotificationType.reportReceived + ? ReportNotificationCard( + notification: notification, + onTap: () => controller.read(notification), + ) + : NotificationCard( + notification: notification, + onTap: () => controller.read(notification), ); - }, - ); - }, - ), - ), - AppGap.v16, - ValueListenableBuilder( - valueListenable: curIndex, - builder: (_, _, _) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate( - pageCount, - (index) => Padding( - padding: AppPadding.indicatorSpacing, - child: Text( - "${index + 1}", - style: AppTextStyles.pageIndicator( - isCurrent: curIndex.value == index, - ), - ), - ), - ), - ); }, ), - AppGap.v16, + ), + ); + } +} + +class _EmptyView extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + children: [ + const Spacer(flex: 153), + Image.asset(AppIcon.message.path), + Text( + "지금은 알려드릴 게 없어요", + style: AppTextStyles.textMedium(textColor: AppColors.gray60), + ), + const Spacer(flex: 275), ], ); } } + +class _ErrorView extends StatelessWidget { + final String message; + final VoidCallback onRetry; + + const _ErrorView({required this.message, required this.onRetry}); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + message, + style: AppTextStyles.textMedium(textColor: AppColors.gray60), + ), + AppGap.v16, + TextButton( + onPressed: onRetry, + child: Text( + '다시 시도', + style: AppTextStyles.textMedium(textColor: AppColors.primary), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/notification/states/notification_state.dart b/lib/presentation/notification/states/notification_state.dart deleted file mode 100644 index 09759229..00000000 --- a/lib/presentation/notification/states/notification_state.dart +++ /dev/null @@ -1,13 +0,0 @@ -//TODO : 서버의 알림 상태 통일시키기 -enum NotificationState { - newComment("누군가가 댓글을 남겼어요"), - requestChat("누군가가 커피챗을 신청했어요"), - reported("신고가 누적되어 커피챗 및 커뮤니티 활동이 제한되었습니다."), - newReview("누군가로부터 리뷰가 왔어요"), - overHeart("게시물의 좋아요 수가 50개를 넘었어요") - ; - - final String title; - - const NotificationState(this.title); -} diff --git a/lib/presentation/notification/widgets/notification_button.dart b/lib/presentation/notification/widgets/notification_button.dart index 2b4af53f..5654a64d 100644 --- a/lib/presentation/notification/widgets/notification_button.dart +++ b/lib/presentation/notification/widgets/notification_button.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; +import 'package:go_router/go_router.dart'; import 'package:ondo/core/design_system/app_colors.dart'; import 'package:ondo/core/design_system/app_icon.dart'; import 'package:ondo/core/design_system/app_layout.dart'; +import 'package:ondo/core/router/app_router.dart'; import 'package:ondo/presentation/notification/controllers/notification_controller.dart'; -import 'package:ondo/presentation/notification/screens/notification_screen.dart'; @immutable class NotificationButton extends GetView { @@ -26,11 +27,7 @@ class NotificationButton extends GetView { shape: RoundedRectangleBorder(borderRadius: AppRadius.baseRadius), minimumSize: Size.square(AppSpacing.s44), ), - onPressed: controller.newNotificationCount.value > 0 - ? () => Get.to( - NotificationScreen(), - ) - : null, + onPressed: () => context.push(RoutePaths.notification), icon: SvgPicture.asset( controller.newNotificationCount.value > 0 ? AppIcon.alarmBrown.path diff --git a/lib/presentation/notification/widgets/notification_card.dart b/lib/presentation/notification/widgets/notification_card.dart index 434e41bd..72ce422d 100644 --- a/lib/presentation/notification/widgets/notification_card.dart +++ b/lib/presentation/notification/widgets/notification_card.dart @@ -8,42 +8,49 @@ import '../../../core/design_system/app_layout.dart'; import '../../../core/design_system/app_text_styles.dart'; class NotificationCard extends StatelessWidget { - final NotificationEntity notificationInfo; + final NotificationEntity notification; final VoidCallback? onTap; const NotificationCard({ super.key, this.onTap, - required this.notificationInfo, + required this.notification, }); @override Widget build(BuildContext context) { return GestureDetector( + behavior: HitTestBehavior.opaque, onTap: onTap, - child: Row( - children: [ - CustomProfileCircle( - radius: AppSpacing.s24, - //TODO : 서버 프로필 이미지 api 개발 이후에 수정 - imageUrl: null, - ), - AppGap.h12, - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _title(), - Text( - notificationInfo.body, - style: AppTextStyles.caption(textColor: AppColors.gray60), - maxLines: 1, - overflow: TextOverflow.ellipsis, + child: Opacity( + opacity: notification.read ? .5 : 1.0, + child: Container( + decoration: BoxDecoration(color: AppColors.white), + child: Row( + children: [ + CustomProfileCircle( + radius: AppSpacing.s24, + //TODO : 서버 프로필 이미지 api 개발 이후에 수정 + imageUrl: null, + ), + AppGap.h12, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _title(), + Text( + notification.body, + style: AppTextStyles.caption(textColor: AppColors.gray60), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], ), - ], - ), + ), + ], ), - ], + ), ), ); } @@ -51,14 +58,14 @@ class NotificationCard extends StatelessWidget { Widget _title() => Row( children: [ Text( - notificationInfo.title, + notification.title, style: AppTextStyles.caption(textColor: AppColors.gray90), maxLines: 1, overflow: TextOverflow.ellipsis, ), Spacer(), Text( - AppDateUtils.timeAgo(notificationInfo.createdAt), + AppDateUtils.timeAgo(notification.createdAt), style: AppTextStyles.caption(textColor: AppColors.gray60), ), ], diff --git a/lib/presentation/notification/widgets/report_notification_card.dart b/lib/presentation/notification/widgets/report_notification_card.dart index 23467b49..1c48b601 100644 --- a/lib/presentation/notification/widgets/report_notification_card.dart +++ b/lib/presentation/notification/widgets/report_notification_card.dart @@ -7,55 +7,58 @@ import '../../../core/design_system/app_layout.dart'; import '../../../core/design_system/app_text_styles.dart'; class ReportNotificationCard extends StatelessWidget { - final NotificationEntity notificationInfo; + final NotificationEntity notification; final VoidCallback? onTap; const ReportNotificationCard({ super.key, this.onTap, - required this.notificationInfo, + required this.notification, }); - @override Widget build(BuildContext context) { return GestureDetector( + behavior: HitTestBehavior.opaque, onTap: onTap, - child: Container( - padding: AppPadding.card, - decoration: BoxDecoration( - borderRadius: AppRadius.baseRadius, - color: AppColors.redLight, - ), - child: Row( - children: [ - CustomProfileCircle( - radius: AppSpacing.s24, - //TODO : 프로필 이미지 api 개발 이후에 수정 - imageUrl: null, - ), - AppGap.h12, - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - notificationInfo.title, - style: AppTextStyles.caption(textColor: AppColors.gray90), - overflow: TextOverflow.ellipsis, - ), - Text( - notificationInfo.body, - style: AppTextStyles.caption(textColor: AppColors.gray70), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], + child: Opacity( + opacity: notification.read ? .5 : 1.0, + child: Container( + padding: AppPadding.card, + decoration: BoxDecoration( + borderRadius: AppRadius.baseRadius, + color: AppColors.redLight, + ), + child: Row( + children: [ + CustomProfileCircle( + radius: AppSpacing.s24, + //TODO : 프로필 이미지 api 개발 이후에 수정 + imageUrl: null, + ), + AppGap.h12, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + notification.title, + style: AppTextStyles.caption(textColor: AppColors.gray90), + overflow: TextOverflow.ellipsis, + ), + Text( + notification.body, + style: AppTextStyles.caption(textColor: AppColors.gray70), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), ), - ), - ], + ], + ), ), ), ); diff --git a/pubspec.lock b/pubspec.lock index 7fffb99f..0a5eff06 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -492,10 +492,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" mime: dependency: transitive description: @@ -785,10 +785,10 @@ packages: dependency: transitive description: name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" timezone: dependency: transitive description: