Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions android/gradle.properties
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions lib/core/constants/notification_type.dart
Original file line number Diff line number Diff line change
@@ -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')
;
Comment thread
eum018 marked this conversation as resolved.

final String value;

const NotificationType(this.value);
}
7 changes: 6 additions & 1 deletion lib/core/router/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -196,7 +197,11 @@ final GoRouter appRouter = GoRouter(
path: RoutePaths.notification,
name: 'notification',
builder: (context, state) {
NotificationBinding().dependencies();
if (Get.isRegistered<NotificationController>()) {
Get.find<NotificationController>().refresh();
} else {
NotificationBinding().dependencies();
}
return const NotificationScreen();
},
),
Expand Down
10 changes: 7 additions & 3 deletions lib/data/models/notification/response/notification_model.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -30,7 +31,7 @@ class NotificationDataModel extends BaseDataModel<NotificationModel> {

class NotificationModel extends BaseModel {
final int id;
final String type;
final NotificationType type;
final String title;
final String body;
final String target;
Expand All @@ -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"],
Expand All @@ -60,7 +64,7 @@ class NotificationModel extends BaseModel {
@override
Map<String, dynamic> toJson() => {
"id": id,
"type": type,
"type": type.value,
"title": title,
"body": body,
"target": target,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -17,7 +18,7 @@ class NotificationRepositoryImpl extends NotificationRepository {
// ─── 서버 알림 ─────────────────────────────────────────────────

@override
Future<List<NotificationEntity>> loadMyNotificationModel(
Future<ListableWrapper<NotificationEntity>> loadMyNotificationModel(
int size,
int page,
) async {
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion lib/domain/entities/notification/notification_entity.dart
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<List<NotificationEntity>> loadMyNotificationModel(int size, int page);
Future<ListableWrapper<NotificationEntity>> loadMyNotificationModel(
int size,
int page,
);

Future<int> loadUnreadNotificationCount();

Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -6,10 +7,10 @@ class LoadMyNotificationListUseCase {

LoadMyNotificationListUseCase({required this.repository});

Future<List<NotificationEntity>> call({
Future<ListableWrapper<NotificationEntity>> call({
required int size,
required int page,
}) async {
return (await repository.loadMyNotificationModel(size, page));
}) {
return repository.loadMyNotificationModel(size, page);
}
}
1 change: 1 addition & 0 deletions lib/presentation/home/widgets/home_post_rank_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
156 changes: 112 additions & 44 deletions lib/presentation/notification/controllers/notification_controller.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<NotificationEntity> viewNotificationList =
<NotificationEntity>[].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;
Expand All @@ -29,68 +40,125 @@ class NotificationController extends GetxController {

@override
void onInit() {
_loadMyNotificationList();
_loadUnreadNotificationCount();
refresh();
super.onInit();
}

Future<void> _loadMyNotificationList() async {
//TODO : 화면 연동 과정에서 범위 맞추기
viewNotificationList.assignAll(
await loadMyNotificationListUseCase.call(size: 20, page: 0),
);
@override
Future<void> refresh() async {
await Future.wait([
_loadMyNotificationList(),
_loadUnreadNotificationCount(),
]);
}

Future<void> _loadUnreadNotificationCount() async {
newNotificationCount.value = await loadUnreadNotificationCountUseCase
.call();
}
Future<void> _loadMyNotificationList() async {
_currentPage = 0;
_isLast = false;
errorMessage.value = '';
isLoading.value = true;

Future<void> _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<bool> _readNotification(int id) async {
return await readNotificationUseCase.call(id);
Future<void> 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;
}
}
Comment thread
eum018 marked this conversation as resolved.

Future<bool> _deleteAllReadNotification() async {
return deleteAllReadNotificationsUseCase.call();
Future<void> _loadUnreadNotificationCount() async {
try {
newNotificationCount.value = await loadUnreadNotificationCountUseCase();
} catch (e) {
debugPrint('[NotificationController] 읽지 않은 알림 수 조회 실패 - error: $e');
}
}

Future<void> 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<void> 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<void> 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<void> 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<bool> _readNotification(int id) async =>
readNotificationUseCase(id);

Future<bool> _deleteAllReadNotification() async =>
deleteAllReadNotificationsUseCase();
}
Loading
Loading