Repository: devlink-community/gaesubang-app
Pull request #60: [feat] 50/communitylist 화면 생성
Author: login: Eastar-DS
Labels: name: 🚀 feat
description: 기능추가
Created: 2025-05-02T06:24:20Z | Merged: 2025-05-02T06:26:44Z

## 🚀 주요 변경사항
- 게시판에서 사용할 모델 생성
- 게시판 리스트 화면 생성

## 📷 동작 화면/캡처(옵션)
<img width="599" alt="image" src="https://github.com/user-attachments/assets/0dcd689e-5aa5-4301-a356-3fc38042e020" />


## 💬 참고/이슈 링크(선택)
- 관련 이슈: #50 




Diff:
diff --git a/lib/community/community.md b/lib/community/community.md
new file mode 100644
index 00000000..0481bb4a
--- /dev/null
+++ b/lib/community/community.md
@@ -0,0 +1,43 @@
+아래 내 요구사항을 반영해서 코드를 다시 작성해줘.
+
+1.  dataSource에서 sort파라미터를 받지 않고 모든 postList를 제공하는 메소드로 수정해줘.
+2.  PostDto도 freezed 3.0.6 버전을 이용해서 작성해줘.
+3.  id는 int 말고 String 타입으로 만들어줘.
+4.  내가 사용할 모델인 Post의 필드값들은 아래와 같아. 필요한 필드값이 있다면 더 추가해줘도 돼.
+    id, String
+    title, String
+    content, String
+    member, <Member>
+    boardType, enum
+    createdAt, DateTime
+    hashTag, List<HashTag>
+    like, List<Like>
+5.  RepositoryImpl의 필드값에는 외부에서 주입해주는 DataSource가 존재해. 유즈케이스에서는 내가 말한대로 만들어준거같은데, namedParameter를 이용하면 더 좋을거같아.
+6.  내가 다루는 CommunityListState의 필드값에 AsyncValue가 들어가면 Notifier파일에서 asyncNotifier 보다 notifier를 사용해서 구현하는게 더 섬세한 부분을 커버해줄거 같은데, 만약 내말이 맞다면 그렇게 구현해줘.
+
+
+
+아래 내 요구사항을 반영해서 코드를 다시 작성해줘.
+1. DTO는 nullable하게 구현하는게 좋을거같아.
+2. DTO에 있는 Member 필드는 lib/auth를 담당하고있는 다른 사람이 작성하고있어서 내가 작성하면 안될것같아. 일단 목데이터같은걸로 사용할 수 있게 구현해줘. Member모델에는 id, email, nickname, uid, onAir, image 필드가 존재해.
+3. 내가 담당해서 만들 모델들인 Like, Comment, HashTag모델에서 다루는 필드값은 아래와 같아.
+   Table Like {
+   boardId, String, , ;
+   memberId, String, , ;
+   }
+   Table Comment {
+   boardId, String, , ;
+   memberId, String, , ;
+   createdAt, DateTime, , ;
+   content, String, , ;
+   }
+   Table HashTag {
+   id, String, , ;
+   content, String, , ;
+   }
+4. import할때 경로는 가능한 package:로 시작하는 절대경로로 작성해줘.
+5. presentation에 만든 action, state등의 파일들은 presentation/community_list 폴더로 이동시켜줘. 나중에 community_detail등 다른 폴더도 만들거거든.
+
+아래 내 요구사항을 반영해서 코드를 다시 작성해줘.
+1. 일단 내가 사용할 Member 모델도 만들어줘.
+2. 내가 사용할 enum들은 module/util에 dart파일로 따로따로 만들어줘.
diff --git a/lib/community/community_main.dart b/lib/community/community_main.dart
new file mode 100644
index 00000000..e61211c8
--- /dev/null
+++ b/lib/community/community_main.dart
@@ -0,0 +1,21 @@
+import 'package:devlink_mobile_app/community/module/community_router.dart';
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+
+void main() {
+  runApp(const ProviderScope(child: MyApp()));
+}
+
+class MyApp extends ConsumerWidget {
+  const MyApp({super.key});
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final router = ref.watch(communityRouterProvider);
+    return MaterialApp.router(
+      title: '커뮤니티 스크린',
+      routerConfig: router,
+      theme: ThemeData(),
+    );
+  }
+}
diff --git a/lib/community/data/data_source/mock_post_data_source_impl.dart b/lib/community/data/data_source/mock_post_data_source_impl.dart
new file mode 100644
index 00000000..7d9cf1a6
--- /dev/null
+++ b/lib/community/data/data_source/mock_post_data_source_impl.dart
@@ -0,0 +1,44 @@
+// lib/community/data/data_source/post_data_source_impl.dart
+import 'dart:math';
+import 'package:devlink_mobile_app/community/data/dto/hash_tag_dto.dart';
+import 'package:devlink_mobile_app/community/data/dto/like_dto.dart';
+import 'package:devlink_mobile_app/community/data/dto/member_dto.dart';
+import 'package:devlink_mobile_app/community/data/dto/post_dto.dart';
+import 'package:devlink_mobile_app/community/module/util/board_type_enum.dart';
+
+import 'post_data_source.dart';
+
+class PostDataSourceImpl implements PostDataSource {
+  @override
+  Future<List<PostDto>> fetchPostList() async {
+    await Future<void>.delayed(const Duration(milliseconds: 500));
+    final rand = Random();
+
+    return List.generate(30, (i) {
+      final likeCnt = rand.nextInt(200);
+      return PostDto(
+        id: 'post_$i',
+        title: '목 게시글 $i',
+        content: '[Mock] 내용 $i',
+        member: MemberDto(            // auth 완성 전까지 임시
+          id: 'u$i',
+          email: 'user$i@mail.com',
+          nickname: 'user$i',
+          uid: 'uid_$i',
+          onAir: i.isEven,
+          image: '',
+        ),
+        boardType: BoardType.free,
+        createdAt: DateTime.now().subtract(Duration(minutes: i * 5)),
+        hashTag: [
+          HashTagDto(id: 't1', content: '#태그${i % 3}'),
+          if (i.isEven) HashTagDto(id: 't2', content: '#인기'),
+        ],
+        like: List.generate(
+          likeCnt,
+          (idx) => LikeDto(boardId: 'post_$i', memberId: 'u$idx'),
+        ),
+      );
+    });
+  }
+}
diff --git a/lib/community/data/data_source/post_data_source.dart b/lib/community/data/data_source/post_data_source.dart
new file mode 100644
index 00000000..ac4292c0
--- /dev/null
+++ b/lib/community/data/data_source/post_data_source.dart
@@ -0,0 +1,7 @@
+// lib/community/data/data_source/post_data_source.dart
+import 'package:devlink_mobile_app/community/data/dto/post_dto.dart';
+
+abstract interface class PostDataSource {
+  /// 모든 게시글을 한 번에 가져온다.
+  Future<List<PostDto>> fetchPostList();
+}
diff --git a/lib/community/data/dto/comment_dto.dart b/lib/community/data/dto/comment_dto.dart
new file mode 100644
index 00000000..5c22d321
--- /dev/null
+++ b/lib/community/data/dto/comment_dto.dart
@@ -0,0 +1,16 @@
+// lib/community/data/dto/comment_dto.dart
+import 'package:freezed_annotation/freezed_annotation.dart';
+part 'comment_dto.freezed.dart';
+part 'comment_dto.g.dart';
+@freezed
+abstract class CommentDto with _$CommentDto {
+  const factory CommentDto({
+    String?   boardId,
+    String?   memberId,
+    DateTime? createdAt,
+    String?   content,
+  }) = _CommentDto;
+
+  factory CommentDto.fromJson(Map<String, dynamic> json) =>
+      _$CommentDtoFromJson(json);
+}
diff --git a/lib/community/data/dto/hash_tag_dto.dart b/lib/community/data/dto/hash_tag_dto.dart
new file mode 100644
index 00000000..2bda1595
--- /dev/null
+++ b/lib/community/data/dto/hash_tag_dto.dart
@@ -0,0 +1,15 @@
+// lib/community/data/dto/hash_tag_dto.dart
+import 'package:freezed_annotation/freezed_annotation.dart';
+part 'hash_tag_dto.freezed.dart';
+part 'hash_tag_dto.g.dart';
+
+@freezed
+abstract class HashTagDto with _$HashTagDto {
+  const factory HashTagDto({
+    String? id,
+    String? content,
+  }) = _HashTagDto;
+
+  factory HashTagDto.fromJson(Map<String, dynamic> json) =>
+      _$HashTagDtoFromJson(json);
+}
diff --git a/lib/community/data/dto/like_dto.dart b/lib/community/data/dto/like_dto.dart
new file mode 100644
index 00000000..c45f63fd
--- /dev/null
+++ b/lib/community/data/dto/like_dto.dart
@@ -0,0 +1,15 @@
+// lib/community/data/dto/like_dto.dart
+import 'package:freezed_annotation/freezed_annotation.dart';
+part 'like_dto.freezed.dart';
+part 'like_dto.g.dart';
+
+@freezed
+abstract class LikeDto with _$LikeDto {
+  const factory LikeDto({
+    String? boardId,
+    String? memberId,
+  }) = _LikeDto;
+
+  factory LikeDto.fromJson(Map<String, dynamic> json) =>
+      _$LikeDtoFromJson(json);
+}
diff --git a/lib/community/data/dto/member_dto.dart b/lib/community/data/dto/member_dto.dart
new file mode 100644
index 00000000..6bbacf98
--- /dev/null
+++ b/lib/community/data/dto/member_dto.dart
@@ -0,0 +1,20 @@
+// lib/community/data/dto/member_dto.dart
+import 'package:freezed_annotation/freezed_annotation.dart';
+part 'member_dto.freezed.dart';
+part 'member_dto.g.dart';
+
+/// 실제 Member 모델은 auth 모듈에서 교체 예정!
+@freezed
+abstract class MemberDto with _$MemberDto {
+  const factory MemberDto({
+    String? id,
+    String? email,
+    String? nickname,
+    String? uid,
+    bool?   onAir,
+    String? image,
+  }) = _MemberDto;
+
+  factory MemberDto.fromJson(Map<String, dynamic> json) =>
+      _$MemberDtoFromJson(json);
+}
diff --git a/lib/community/data/dto/post_dto.dart b/lib/community/data/dto/post_dto.dart
new file mode 100644
index 00000000..50f9749b
--- /dev/null
+++ b/lib/community/data/dto/post_dto.dart
@@ -0,0 +1,29 @@
+// lib/community/data/dto/post_dto.dart
+import 'package:devlink_mobile_app/community/data/dto/hash_tag_dto.dart';
+import 'package:devlink_mobile_app/community/data/dto/like_dto.dart';
+import 'package:devlink_mobile_app/community/data/dto/member_dto.dart';
+import 'package:devlink_mobile_app/community/module/util/board_type_enum.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+
+part 'post_dto.freezed.dart';
+part 'post_dto.g.dart';
+
+/// 게시글 DTO (모든 필드 nullable)
+@freezed
+abstract class PostDto with _$PostDto {
+  const factory PostDto({
+    String? id,
+    String? title,
+    String? content,
+    MemberDto? member,               // 👉 auth 완성 전까지 임시 DTO
+    BoardType? boardType,
+    DateTime? createdAt,
+    List<HashTagDto>? hashTag,
+    List<LikeDto>? like,
+  }) = _PostDto;
+
+  factory PostDto.fromJson(Map<String, dynamic> json) =>
+      _$PostDtoFromJson(json);
+}
+
diff --git a/lib/community/data/mapper/post_mapper.dart b/lib/community/data/mapper/post_mapper.dart
new file mode 100644
index 00000000..1f6aec93
--- /dev/null
+++ b/lib/community/data/mapper/post_mapper.dart
@@ -0,0 +1,48 @@
+// lib/community/data/mapper/post_mapper.dart
+// import 'package:auth/domain/model/member.dart';
+import 'package:devlink_mobile_app/auth/domain/model/member.dart';
+import 'package:devlink_mobile_app/community/data/dto/hash_tag_dto.dart';
+import 'package:devlink_mobile_app/community/data/dto/like_dto.dart';
+import 'package:devlink_mobile_app/community/data/dto/member_dto.dart';
+import 'package:devlink_mobile_app/community/data/dto/post_dto.dart';
+import 'package:devlink_mobile_app/community/domain/model/hash_tag.dart';
+import 'package:devlink_mobile_app/community/domain/model/like.dart';
+import 'package:devlink_mobile_app/community/domain/model/post.dart';
+import 'package:devlink_mobile_app/community/module/util/board_type_enum.dart';
+
+extension PostDtoMapper on PostDto {
+  Post toModel() => Post(
+        id: id ?? '',
+        title: title ?? '',
+        content: content ?? '',
+        member: member?.toModel() ??
+            Member(id: '', email: '', nickname: '', uid: '', onAir: false, image: ''),
+        boardType: boardType ?? BoardType.free,
+        createdAt: createdAt ?? DateTime.now(),
+        hashTag: (hashTag ?? []).map((e) => e.toModel()).toList(),
+        like: (like ?? []).map((e) => e.toModel()).toList(),
+      );
+}
+
+extension on MemberDto {
+  Member toModel() => Member(
+        id: id ?? '',
+        email: email ?? '',
+        nickname: nickname ?? '',
+        uid: uid ?? '',
+        onAir: onAir ?? false,
+        image: image ?? '',
+      );
+}
+
+extension on HashTagDto {
+  HashTag toModel() => HashTag(id: id ?? '', content: content ?? '');
+}
+
+extension on LikeDto {
+  Like toModel() => Like(boardId: boardId ?? '', memberId: memberId ?? '');
+}
+
+extension PostDtoListX on List<PostDto> {
+  List<Post> toModelList() => map((e) => e.toModel()).toList();
+}
diff --git a/lib/community/data/repository_impl/post_repository_impl.dart b/lib/community/data/repository_impl/post_repository_impl.dart
new file mode 100644
index 00000000..0cbc6d69
--- /dev/null
+++ b/lib/community/data/repository_impl/post_repository_impl.dart
@@ -0,0 +1,23 @@
+// lib/community/data/repository_impl/post_repository_impl.dart
+import 'package:devlink_mobile_app/community/data/data_source/post_data_source.dart';
+import 'package:devlink_mobile_app/community/data/mapper/post_mapper.dart';
+import 'package:devlink_mobile_app/community/domain/model/post.dart';
+import 'package:devlink_mobile_app/community/domain/repository/post_repository.dart';
+import 'package:devlink_mobile_app/core/result/result.dart';
+
+
+class PostRepositoryImpl implements PostRepository {
+  const PostRepositoryImpl({required PostDataSource dataSource}) : _remote = dataSource;
+
+  final PostDataSource _remote;
+
+  @override
+  Future<Result<List<Post>>> loadPostList() async {
+    try {
+      final dto = await _remote.fetchPostList();
+      return Result.success(dto.toModelList());
+    } catch (e) {
+      return Result.error(mapExceptionToFailure(e, StackTrace.fromString(e.toString())));
+    }
+  }
+}
diff --git a/lib/community/domain/model/comment.dart b/lib/community/domain/model/comment.dart
new file mode 100644
index 00000000..0a5f3501
--- /dev/null
+++ b/lib/community/domain/model/comment.dart
@@ -0,0 +1,13 @@
+// lib/community/domain/model/comment.dart
+import 'package:freezed_annotation/freezed_annotation.dart';
+part 'comment.freezed.dart';
+
+@freezed
+abstract class Comment with _$Comment {
+  const factory Comment({
+    required String boardId,
+    required String memberId,
+    required DateTime createdAt,
+    required String content,
+  }) = _Comment;
+}
diff --git a/lib/community/domain/model/hash_tag.dart b/lib/community/domain/model/hash_tag.dart
new file mode 100644
index 00000000..a5c0aade
--- /dev/null
+++ b/lib/community/domain/model/hash_tag.dart
@@ -0,0 +1,11 @@
+// lib/community/domain/model/hash_tag.dart
+import 'package:freezed_annotation/freezed_annotation.dart';
+part 'hash_tag.freezed.dart';
+
+@freezed
+abstract class HashTag with _$HashTag {
+  const factory HashTag({
+    required String id,
+    required String content,
+  }) = _HashTag;
+}
diff --git a/lib/community/domain/model/like.dart b/lib/community/domain/model/like.dart
new file mode 100644
index 00000000..46593291
--- /dev/null
+++ b/lib/community/domain/model/like.dart
@@ -0,0 +1,11 @@
+// lib/community/domain/model/like.dart
+import 'package:freezed_annotation/freezed_annotation.dart';
+part 'like.freezed.dart';
+
+@freezed
+abstract class Like with _$Like {
+  const factory Like({
+    required String boardId,
+    required String memberId,
+  }) = _Like;
+}
diff --git a/lib/community/domain/model/post.dart b/lib/community/domain/model/post.dart
new file mode 100644
index 00000000..30be5695
--- /dev/null
+++ b/lib/community/domain/model/post.dart
@@ -0,0 +1,26 @@
+// lib/community/domain/model/post.dart
+import 'package:devlink_mobile_app/auth/domain/model/member.dart';
+import 'package:devlink_mobile_app/community/domain/model/hash_tag.dart';
+import 'package:devlink_mobile_app/community/domain/model/like.dart';
+import 'package:devlink_mobile_app/community/module/util/board_type_enum.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+
+
+part 'post.freezed.dart';
+
+@freezed
+abstract class Post with _$Post {
+  const factory Post({
+    required String id,
+    required String title,
+    required String content,
+    required Member member,
+    required BoardType boardType,
+    required DateTime createdAt,
+    @Default(<HashTag>[]) List<HashTag> hashTag,
+    @Default(<Like>[])    List<Like>    like,
+  }) = _Post;
+}
+
+
diff --git a/lib/community/domain/repository/post_repository.dart b/lib/community/domain/repository/post_repository.dart
new file mode 100644
index 00000000..d3dabf82
--- /dev/null
+++ b/lib/community/domain/repository/post_repository.dart
@@ -0,0 +1,9 @@
+// lib/community/domain/repository/post_repository.dart
+import 'package:devlink_mobile_app/community/domain/model/post.dart';
+import 'package:devlink_mobile_app/core/result/result.dart';
+
+
+
+abstract interface class PostRepository {
+  Future<Result<List<Post>>> loadPostList();
+}
diff --git a/lib/community/domain/usecase/load_post_list_use_case.dart b/lib/community/domain/usecase/load_post_list_use_case.dart
new file mode 100644
index 00000000..8f507eeb
--- /dev/null
+++ b/lib/community/domain/usecase/load_post_list_use_case.dart
@@ -0,0 +1,11 @@
+// lib/community/domain/usecase/load_post_list_use_case.dart
+import 'package:devlink_mobile_app/community/domain/model/post.dart';
+import 'package:devlink_mobile_app/community/domain/repository/post_repository.dart';
+import 'package:devlink_mobile_app/core/result/result.dart';
+
+class LoadPostListUseCase {
+  const LoadPostListUseCase({required PostRepository repo}) : _repo = repo;
+  final PostRepository _repo;
+
+  Future<Result<List<Post>>> execute() => _repo.loadPostList();
+}
diff --git a/lib/community/domain/usecase/switch_tab_use_case.dart b/lib/community/domain/usecase/switch_tab_use_case.dart
new file mode 100644
index 00000000..3c95b8db
--- /dev/null
+++ b/lib/community/domain/usecase/switch_tab_use_case.dart
@@ -0,0 +1,5 @@
+import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+
+class SwitchTabUseCase {
+  CommunityTabType execute(CommunityTabType current, CommunityTabType next) => next;
+}
diff --git a/lib/community/module/community_di.dart b/lib/community/module/community_di.dart
new file mode 100644
index 00000000..4629b40d
--- /dev/null
+++ b/lib/community/module/community_di.dart
@@ -0,0 +1,25 @@
+import 'package:devlink_mobile_app/community/data/data_source/mock_post_data_source_impl.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:riverpod_annotation/riverpod_annotation.dart';
+import '../data/data_source/post_data_source.dart';
+
+import '../data/repository_impl/post_repository_impl.dart';
+import '../domain/repository/post_repository.dart';
+import '../domain/usecase/load_post_list_use_case.dart';
+import '../domain/usecase/switch_tab_use_case.dart';
+
+part 'community_di.g.dart';
+
+@riverpod
+PostDataSource postDataSource(Ref ref) => PostDataSourceImpl();
+
+@riverpod
+PostRepository postRepository(Ref ref) =>
+    PostRepositoryImpl(dataSource: ref.watch(postDataSourceProvider));
+
+@riverpod
+LoadPostListUseCase loadPostListUseCase(Ref ref) =>
+    LoadPostListUseCase(repo: ref.watch(postRepositoryProvider));
+
+@riverpod
+SwitchTabUseCase switchTabUseCase(Ref ref) => SwitchTabUseCase();
diff --git a/lib/community/module/community_router.dart b/lib/community/module/community_router.dart
new file mode 100644
index 00000000..b1c2e771
--- /dev/null
+++ b/lib/community/module/community_router.dart
@@ -0,0 +1,15 @@
+import 'package:go_router/go_router.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import '../presentation/community_list/community_list_screen_root.dart';
+
+final communityRoutes = [
+  GoRoute(
+    path: '/community',
+    builder: (context, state) => const CommunityListScreenRoot(),
+  ),
+  // 추가 상세/검색/글쓰기 경로는 이후 구현
+];
+
+final communityRouterProvider = Provider((ref) {
+  return GoRouter(initialLocation: '/community', routes: communityRoutes);
+});
diff --git "a/lib/community/module/util/\bcommunity_tab_type_enum.dart" "b/lib/community/module/util/\bcommunity_tab_type_enum.dart"
new file mode 100644
index 00000000..cea079cc
--- /dev/null
+++ "b/lib/community/module/util/\bcommunity_tab_type_enum.dart"
@@ -0,0 +1 @@
+enum CommunityTabType { popular, newest }
\ No newline at end of file
diff --git a/lib/community/module/util/board_type_enum.dart b/lib/community/module/util/board_type_enum.dart
new file mode 100644
index 00000000..4cb0fce0
--- /dev/null
+++ b/lib/community/module/util/board_type_enum.dart
@@ -0,0 +1 @@
+enum BoardType { notice, free, qna }
\ No newline at end of file
diff --git a/lib/community/presentation/community_list/community_list_action.dart b/lib/community/presentation/community_list/community_list_action.dart
new file mode 100644
index 00000000..2085bddc
--- /dev/null
+++ b/lib/community/presentation/community_list/community_list_action.dart
@@ -0,0 +1,12 @@
+import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+part 'community_list_action.freezed.dart';
+
+@freezed
+sealed class CommunityListAction with _$CommunityListAction {
+  const factory CommunityListAction.refresh() = Refresh;
+  const factory CommunityListAction.changeTab(CommunityTabType tab) = ChangeTab;
+  const factory CommunityListAction.tapPost(String postId) = TapPost;
+  const factory CommunityListAction.tapSearch() = TapSearch;
+  const factory CommunityListAction.tapWrite() = TapWrite;
+}
diff --git a/lib/community/presentation/community_list/community_list_notifier.dart b/lib/community/presentation/community_list/community_list_notifier.dart
new file mode 100644
index 00000000..41ca67cf
--- /dev/null
+++ b/lib/community/presentation/community_list/community_list_notifier.dart
@@ -0,0 +1,68 @@
+// lib/community/presentation/community_list_notifier.dart
+import 'dart:async';
+import 'package:devlink_mobile_app/community/domain/model/post.dart';
+import 'package:devlink_mobile_app/community/domain/usecase/load_post_list_use_case.dart';
+import 'package:devlink_mobile_app/community/module/community_di.dart';
+import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+import 'package:devlink_mobile_app/community/presentation/community_list/community_list_action.dart';
+import 'package:devlink_mobile_app/community/presentation/community_list/community_list_state.dart';
+import 'package:devlink_mobile_app/core/result/result.dart';
+import 'package:riverpod_annotation/riverpod_annotation.dart';
+
+
+part 'community_list_notifier.g.dart';
+
+@riverpod
+class CommunityListNotifier extends _$CommunityListNotifier {
+  @override
+  CommunityListState build() {
+    _loadPostListUseCase = ref.watch(loadPostListUseCaseProvider);
+    // 비동기 로딩 → 결과 반영
+    Future.microtask(_fetch);
+    return const CommunityListState(); // 초기값
+  }
+
+  late final LoadPostListUseCase _loadPostListUseCase;
+
+  /// 원격 새로고침
+  Future<void> _fetch() async {
+    state = state.copyWith(postList: const AsyncLoading());
+    final result = await _loadPostListUseCase.execute();
+    switch (result) {
+      case Success(data: final list):
+        state = state.copyWith(postList: AsyncData(list));
+      case Error(failure: final f):
+        state = state.copyWith(postList: AsyncError(f, StackTrace.current));
+    }
+  }
+
+  /// 탭 변경·수동 새로고침 등 외부 Action 진입점
+  Future<void> onAction(CommunityListAction action) async {
+    switch (action) {
+      case Refresh():
+        await _fetch();
+      case ChangeTab(:final tab):
+        state = state.copyWith(
+          currentTab: tab,
+          postList: state.postList.maybeWhen(
+            data: (list) => AsyncData(_applySort(list, tab)),
+            orElse: () => state.postList,
+          ),
+        );
+      default:
+        // UI 이동 Action 은 Root 에서 처리
+        break;
+    }
+  }
+
+  List<Post> _applySort(List<Post> list, CommunityTabType tab) {
+    switch (tab) {
+      case CommunityTabType.popular:
+        final sorted = [...list]..sort((a, b) => b.like.length.compareTo(a.like.length));
+        return sorted;
+      case CommunityTabType.newest:
+        final sorted = [...list]..sort((a, b) => b.createdAt.compareTo(a.createdAt));
+        return sorted;
+    }
+  }
+}
diff --git a/lib/community/presentation/community_list/community_list_screen.dart b/lib/community/presentation/community_list/community_list_screen.dart
new file mode 100644
index 00000000..ef8e40e5
--- /dev/null
+++ b/lib/community/presentation/community_list/community_list_screen.dart
@@ -0,0 +1,90 @@
+import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+import 'package:devlink_mobile_app/core/result/result.dart';
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'community_list_state.dart';
+import 'community_list_action.dart';
+import '../component/post_list_item.dart';
+
+class CommunityListScreen extends StatelessWidget {
+  const CommunityListScreen({
+    super.key,
+    required this.state,
+    required this.onAction,
+  });
+
+  final CommunityListState state;
+  final void Function(CommunityListAction action) onAction;
+
+  @override
+  Widget build(BuildContext context) {
+    return DefaultTabController(
+      length: 2,
+      child: Scaffold(
+        appBar: AppBar(
+          title: const Text('커뮤니티'),
+          actions: [
+            IconButton(
+              icon: const Icon(Icons.search),
+              onPressed: () => onAction(const CommunityListAction.tapSearch()),
+            ),
+          ],
+        ),
+        floatingActionButton: FloatingActionButton(
+          onPressed: () => onAction(const CommunityListAction.tapWrite()),
+          child: const Icon(Icons.edit),
+        ),
+        body: RefreshIndicator(
+          onRefresh: () async => onAction(const CommunityListAction.refresh()),
+          child: Column(
+            children: [
+              _buildTabBar(),
+              Expanded(
+                child:
+                    _buildPostList() ??
+                    const Center(child: Text('등록된 게시글이 없습니다')),
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
+  Widget _buildTabBar() => TabBar(
+    labelColor: Colors.blue,
+    indicatorColor: Colors.blue,
+    unselectedLabelColor: Colors.grey,
+    onTap:
+        (i) => onAction(
+          CommunityListAction.changeTab(
+            i == 0 ? CommunityTabType.popular : CommunityTabType.newest,
+          ),
+        ),
+    tabs: const [Tab(text: '인기순'), Tab(text: '최신순')],
+  );
+
+  Widget? _buildPostList() {
+    switch (state.postList) {
+      case AsyncLoading():
+        return const Center(child: CircularProgressIndicator());
+      case AsyncError(:final error):
+        return Center(child: Text((error as Failure).message));
+      case AsyncData(:final value):
+        final list = value.isNotEmpty ? value : [];
+        if (list.isEmpty) {
+          return const Center(child: Text('등록된 게시글이 없습니다'));
+        }
+        return ListView.builder(
+          physics: const AlwaysScrollableScrollPhysics(),
+          itemCount: list.length,
+          itemBuilder:
+              (context, i) => PostListItem(
+                post: list[i],
+                onTap: () => onAction(CommunityListAction.tapPost(list[i].id)),
+              ),
+        );
+    }
+    return null;
+  }
+}
diff --git a/lib/community/presentation/community_list/community_list_screen_root.dart b/lib/community/presentation/community_list/community_list_screen_root.dart
new file mode 100644
index 00000000..a3e0fd34
--- /dev/null
+++ b/lib/community/presentation/community_list/community_list_screen_root.dart
@@ -0,0 +1,33 @@
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+
+import 'community_list_action.dart';
+import 'community_list_notifier.dart';
+import 'community_list_screen.dart';
+
+class CommunityListScreenRoot extends ConsumerWidget {
+  const CommunityListScreenRoot({super.key});
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final state = ref.watch(communityListNotifierProvider);
+    final notifier = ref.watch(communityListNotifierProvider.notifier);
+
+    return CommunityListScreen(
+      state: state,
+      onAction: (action) async {
+        switch (action) {
+          case TapPost(:final postId):
+            context.push('/community/$postId');                             // 상세 화면 경로 예시
+          case TapSearch():
+            context.push('/community/search');
+          case TapWrite():
+            context.push('/community/write');
+          default:
+            await notifier.onAction(action);
+        }
+      },
+    );
+  }
+}
diff --git a/lib/community/presentation/community_list/community_list_state.dart b/lib/community/presentation/community_list/community_list_state.dart
new file mode 100644
index 00000000..2d7ca7e0
--- /dev/null
+++ b/lib/community/presentation/community_list/community_list_state.dart
@@ -0,0 +1,17 @@
+import 'package:devlink_mobile_app/community/domain/model/post.dart';
+import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+
+
+part 'community_list_state.freezed.dart';
+
+
+
+@freezed
+abstract class CommunityListState with _$CommunityListState {
+  const factory CommunityListState({
+    @Default(AsyncValue<List<Post>>.loading()) AsyncValue<List<Post>> postList,
+    @Default(CommunityTabType.popular) CommunityTabType currentTab,
+  }) = _CommunityListState;
+}
diff --git a/lib/community/presentation/component/post_list_item.dart b/lib/community/presentation/component/post_list_item.dart
new file mode 100644
index 00000000..26864ed4
--- /dev/null
+++ b/lib/community/presentation/component/post_list_item.dart
@@ -0,0 +1,29 @@
+import 'package:devlink_mobile_app/community/domain/model/post.dart';
+import 'package:flutter/material.dart';
+
+
+class PostListItem extends StatelessWidget {
+  const PostListItem({
+    super.key,
+    required this.post,
+    required this.onTap,
+  });
+
+  final Post post;
+  final VoidCallback onTap;
+
+  @override
+  Widget build(BuildContext context) => ListTile(
+        title: Text(post.title),
+        subtitle: Text('${post.member.nickname} · ${post.createdAt.toLocal()}'),
+        trailing: Row(
+          mainAxisSize: MainAxisSize.min,
+          children: [
+            const Icon(Icons.favorite_border, size: 16),
+            const SizedBox(width: 4),
+            Text('${post.like.length}'),
+          ],
+        ),
+        onTap: onTap,
+      );
+}


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 6**

<details>
<summary>🧹 Nitpick comments (29)</summary><blockquote>

<details>
<summary>lib/community/module/util/board_type_enum.dart (1)</summary><blockquote>

`1-1`: **게시판 유형 열거형이 명확합니다.**

`BoardType` 열거형이 `notice`(공지사항), `free`(자유게시판), `qna`(질문과 답변) 세 가지 값을 정의하고 있습니다. 이는 커뮤니티 내에서 게시글의 종류를 구분하는 데 사용될 것으로 보입니다.

여기에도 각 열거형 값에 대한 문서화를 추가하면 코드의 의도를 더 명확하게 전달할 수 있을 것입니다:

```dart
/// 게시판 유형을 정의하는 열거형
enum BoardType {
  /// 공지사항 게시판
  notice,
  /// 자유 게시판
  free,
  /// 질문과 답변 게시판
  qna,
}
```

이렇게 문서화를 추가하면 향후 팀원이나 새로운 개발자가 코드를 이해하는 데 도움이 됩니다.

</blockquote></details>
<details>
<summary>lib/community/data/dto/hash_tag_dto.dart (1)</summary><blockquote>

`1-15`: **Freezed 패키지를 활용한 DTO 구현이 잘 되어 있습니다.**

`HashTagDto` 클래스는 `freezed` 패키지를 활용하여 불변 객체(immutable object)로 구현되어 있습니다. 이는 데이터 무결성을 유지하는 데 도움이 됩니다. 몇 가지 개선 사항을 제안해 드립니다:

1. 각 필드에 대한 문서화 추가:
```dart
@freezed
abstract class HashTagDto with _$HashTagDto {
  const factory HashTagDto({
    /// 해시태그의 고유 식별자
    String? id,
    /// 해시태그 내용 (예: '#개발자')
    String? content,
  }) = _HashTagDto;

  factory HashTagDto.fromJson(Map<String, dynamic> json) =>
      _$HashTagDtoFromJson(json);
}
```

2. `content` 필드에 대한 유효성 검사도 고려해볼 수 있습니다. 보통 해시태그는 '#'로 시작하는데, 이 부분을 검증하는 로직이 필요할 수 있습니다. 이는 DTO가 아닌 도메인 모델에서 처리하는 것이 더 적절할 수 있습니다.

3. DTO에서 널 허용 필드(`String?`)를 사용한 것은 외부 데이터 소스와의 통합 시 널 값이 올 수 있기 때문에 적절한 선택입니다. 하지만 도메인 모델로 변환할 때는 적절한 기본값을 제공하거나 유효성 검사를 수행하는 것이 좋습니다.

</blockquote></details>
<details>
<summary>lib/community/data/data_source/post_data_source.dart (1)</summary><blockquote>

`1-7`: **데이터 소스 인터페이스가 명확하게 정의되어 있습니다.**

`PostDataSource` 인터페이스는 게시글 목록을 가져오는 기능을 정의하고 있습니다. 인터페이스 설계가 간결하고 목적이 명확합니다. 코드에 대한 몇 가지 제안 사항입니다:

1. 문서화가 잘 되어 있어 메서드의 목적을 이해하기 쉽습니다. 다만, 반환 값과 예외 처리에 대한 추가 정보를 제공하면 더 좋을 것 같습니다:

```dart
abstract interface class PostDataSource {
  /// 모든 게시글을 한 번에 가져온다.
  /// 
  /// 반환: 게시글 DTO 목록
  /// 예외: 네트워크 오류 또는 서버 오류 발생 시 예외 발생 가능
  Future<List<PostDto>> fetchPostList();
}
```

2. 한 번에 모든 게시글을 가져오는 방식은 게시글이 많아질 경우 성능 이슈가 발생할 수 있습니다. 장기적으로는 페이지네이션을 지원하는 메서드도 고려해보세요:

```dart
abstract interface class PostDataSource {
  /// 모든 게시글을 한 번에 가져온다.
  Future<List<PostDto>> fetchPostList();
  
  /// 페이지 단위로 게시글을 가져온다.
  /// 
  /// [page]: 페이지 번호 (0부터 시작)
  /// [size]: 페이지당 게시글 수
  Future<List<PostDto>> fetchPostListByPage(int page, int size);
}
```

3. 게시판 유형이나 탭 유형에 따라 필터링하는 기능도 필요할 수 있습니다:

```dart
Future<List<PostDto>> fetchPostListByBoardType(BoardType boardType);
```

이러한 추가 기능은 현재 요구사항에 따라 필요하지 않을 수 있지만, 확장성을 고려하여 미리 고민해볼 수 있습니다.

</blockquote></details>
<details>
<summary>lib/community/domain/model/hash_tag.dart (1)</summary><blockquote>

`1-11`: **Freezed를 활용한 불변 객체 모델 구현이 잘 되어 있습니다.**

`HashTag` 클래스는 `freezed` 패키지를 사용하여 불변(immutable) 도메인 모델을 잘 구현하셨네요. 불변 객체는 데이터의 일관성을 유지하는 데 큰 도움이 됩니다. 

다만, 추가적으로 다음 사항을 고려해 보시면 좋을 것 같습니다:

1. 클래스와 필드에 대한 문서화 주석을 추가하면 다른 개발자들이 코드를 이해하는 데 도움이 될 것입니다.
2. `id`와 `content`가 어떤 의미를 가지는지, 어떤 제약 조건이 있는지 설명하는 주석이 있으면 좋겠습니다.

예시:
```dart
/// 게시글에 사용되는 해시태그 모델
/// 
/// [id]는 해시태그의 고유 식별자입니다.
/// [content]는 해시태그의 텍스트 내용입니다 (예: '개발', '디자인').
@freezed
abstract class HashTag with _$HashTag {
  const factory HashTag({
    required String id,
    required String content,
  }) = _HashTag;
}
```

</blockquote></details>
<details>
<summary>lib/community/domain/repository/post_repository.dart (1)</summary><blockquote>

`1-9`: **레포지토리 인터페이스 정의가 명확하게 되어 있습니다.**

`PostRepository` 인터페이스가 단일 책임 원칙(Single Responsibility Principle)에 맞게 잘 설계되었습니다. `Result` 타입을 사용하여 성공/실패 상태를 표현한 것도 좋은 방식입니다.

개선할 수 있는 부분:

1. 인터페이스와 메소드의 목적을 설명하는 문서화 주석을 추가하면 좋겠습니다.
2. 5-6번 라인의 불필요한 공백 라인을 제거하는 것이 좋겠습니다.

예시:
```dart
// lib/community/domain/repository/post_repository.dart
import 'package:devlink_mobile_app/community/domain/model/post.dart';
import 'package:devlink_mobile_app/core/result/result.dart';

/// 게시글 관련 데이터 접근을 위한 레포지토리 인터페이스
abstract interface class PostRepository {
  /// 게시글 목록을 불러오는 메소드
  /// 
  /// 성공 시 게시글 목록을 반환하고, 실패 시 에러 정보를 포함한 Result 객체를 반환합니다.
  Future<Result<List<Post>>> loadPostList();
}
```

</blockquote></details>
<details>
<summary>lib/community/data/dto/like_dto.dart (1)</summary><blockquote>

`1-15`: **DTO 클래스 설계가 잘 되어 있습니다.**

`LikeDto` 클래스가 `freezed` 패키지를 사용하여 JSON 직렬화/역직렬화가 가능한 데이터 전송 객체(DTO)로 잘 구현되었습니다. 

초보자를 위한 설명: DTO(Data Transfer Object)는 계층 간 데이터 전송을 위한 객체로, 주로 API 응답을 매핑하는데 사용됩니다. `freezed` 패키지는 불변 객체를 쉽게 생성할 수 있게 도와주는 코드 생성 라이브러리입니다.

개선 제안:

1. 클래스와 필드에 대한 문서화 주석을 추가하면 좋겠습니다.
2. 현재 필드들이 null이 될 수 있게 설계되었는데, API 응답에 따라 적절한 기본값 설정을 고려해 보세요.

예시:
```dart
/// 게시글 좋아요 정보를 위한 데이터 전송 객체
@freezed
abstract class LikeDto with _$LikeDto {
  const factory LikeDto({
    /// 좋아요가 달린 게시글의 ID
    String? boardId,
    /// 좋아요를 누른 회원의 ID
    String? memberId,
  }) = _LikeDto;

  factory LikeDto.fromJson(Map<String, dynamic> json) =>
      _$LikeDtoFromJson(json);
}
```

</blockquote></details>
<details>
<summary>lib/community/domain/model/like.dart (1)</summary><blockquote>

`1-11`: **도메인 모델이 불변 객체로 잘 설계되어 있습니다.**

`Like` 클래스는 `freezed` 패키지를 사용하여 불변성(immutability)을 갖는 도메인 모델로 잘 구현되었습니다. DTO와 달리 모든 필드가 required로 선언되어 null 값을 허용하지 않는 것은 도메인 모델에서 좋은 접근 방식입니다.

초보자를 위한 설명: 도메인 모델은 비즈니스 규칙과 로직을 담당하는 계층의 핵심 객체입니다. DTO는 외부 데이터를 주고받는 용도로 사용되고, 도메인 모델은 애플리케이션 내부에서 사용되는 형태로 변환된 객체입니다.

개선 제안:

1. 클래스와 필드에 대한 문서화 주석을 추가하면 좋겠습니다.
2. 해당 모델의 비즈니스 규칙이나 제약 조건이 있다면 주석으로 설명하는 것이 도움이 됩니다.

예시:
```dart
/// 게시글 좋아요 정보를 나타내는 도메인 모델
@freezed
abstract class Like with _$Like {
  const factory Like({
    /// 좋아요가 달린 게시글의 ID
    required String boardId,
    /// 좋아요를 누른 회원의 ID
    required String memberId,
  }) = _Like;
}
```

</blockquote></details>
<details>
<summary>lib/community/data/dto/comment_dto.dart (1)</summary><blockquote>

`1-16`: **DTO 클래스 구조 좋습니다! 몇 가지 개선 사항 제안해 드릴게요.**

`CommentDto` 클래스가 잘 설계되어 있네요! Freezed 패키지를 활용하여 불변 객체를 만든 것은 매우 좋은 접근입니다. DTO(Data Transfer Object)는 데이터 계층과 도메인 계층 사이에서 데이터를 전달하는 중요한 역할을 하죠.

개선할 수 있는 몇 가지 사항을 제안합니다:

1. 각 필드에 대한 간단한 주석을 추가하면 다른 개발자들이 코드를 더 쉽게 이해할 수 있을 것입니다.
2. `null` 값이 허용되는 필드들에 대한 처리 방법을 도메인 모델로 변환하는 매퍼에서 명확히 정의하면 좋겠습니다.
3. 생성 날짜(createdAt)에 대한 JSON 직렬화/역직렬화 포맷을 명시적으로 지정하는 것을 고려해 보세요. DateTime 객체는 JSON 변환 시 특별한 처리가 필요할 수 있습니다.

```dart
// 예시: DateTime 필드에 대한 명시적 변환기 추가
@JsonKey(fromJson: _dateTimeFromJson, toJson: _dateTimeToJson)
DateTime? createdAt,

// 클래스 외부에 변환 메소드 추가
DateTime? _dateTimeFromJson(String? date) => date == null ? null : DateTime.parse(date);
String? _dateTimeToJson(DateTime? date) => date?.toIso8601String();
```

이렇게 하면 날짜 데이터의 일관성을 더 잘 보장할 수 있습니다.

</blockquote></details>
<details>
<summary>lib/community/domain/model/comment.dart (1)</summary><blockquote>

`1-13`: **도메인 모델이 깔끔하게 정의되었네요!**

`Comment` 도메인 모델이 매우 명확하게 정의되어 있습니다. DTO와 달리 모든 필드가 `required`로 정의되어 있어서 도메인 로직에서 null 값을 처리할 필요가 없어 안전합니다. 이는 좋은 설계 방식입니다!

도메인 모델은 애플리케이션의 핵심 비즈니스 로직을 담당하는 부분이므로, 몇 가지 보완할 점을 제안합니다:

1. 각 필드의 의미와 제약 조건에 대한 간단한 주석을 추가하면 초보자도 쉽게 이해할 수 있을 것입니다.
2. 필요한 경우 댓글 관련 비즈니스 로직을 추가할 수 있는 확장 메소드를 고려해 보세요.

```dart
// 예시: 확장 메소드를 통한 유용한 기능 추가
extension CommentExtension on Comment {
  bool isCreatedByMember(String id) => memberId == id;
  bool isCreatedRecently() => DateTime.now().difference(createdAt).inHours < 24;
  String get formattedCreationTime => '${createdAt.year}년 ${createdAt.month}월 ${createdAt.day}일';
}
```

이런 확장 메소드를 사용하면 UI 레이어에서 데이터를 더 쉽게 활용할 수 있습니다.

</blockquote></details>
<details>
<summary>lib/community/presentation/component/post_list_item.dart (1)</summary><blockquote>

`1-29`: **UI 컴포넌트가 잘 설계되었으나 몇 가지 개선 사항이 있습니다**

`PostListItem` 위젯이 잘 설계되어 있습니다. 각 게시물을 표시하기 위한 기본 정보(제목, 작성자, 작성 시간, 좋아요 수)를 모두 포함하고 있어요.

하지만 사용자 경험을 향상시키기 위해 몇 가지 개선 사항을 제안합니다:

1. 날짜 형식이 현재 `toLocal()`만 사용되어 있어 기본 DateTime 형식으로 표시됩니다. 사용자 친화적인 형식으로 변경하면 좋겠습니다.

```dart
// 예시: 날짜 포맷팅 함수 추가
String _formatDate(DateTime date) {
  final now = DateTime.now();
  final difference = now.difference(date);
  
  if (difference.inDays == 0) {
    if (difference.inHours == 0) {
      return '${difference.inMinutes}분 전';
    }
    return '${difference.inHours}시간 전';
  } else if (difference.inDays < 7) {
    return '${difference.inDays}일 전';
  } else {
    return '${date.year}.${date.month}.${date.day}';
  }
}
```

2. 사용자가 이미 좋아요를 누른 게시물을 구분할 수 있도록 하면 좋겠습니다:

```diff
- const Icon(Icons.favorite_border, size: 16),
+ Icon(
+   post.isLikedByCurrentUser 
+     ? Icons.favorite 
+     : Icons.favorite_border,
+   size: 16,
+   color: post.isLikedByCurrentUser ? Colors.red : null,
+ ),
```

3. 제목이나 작성자 이름이 너무 길 경우 UI가 깨질 수 있으므로 오버플로우 처리를 추가하는 것이 좋겠습니다:

```diff
- title: Text(post.title),
+ title: Text(
+   post.title,
+   overflow: TextOverflow.ellipsis,
+   maxLines: 2,
+ ),
```

이러한 변경사항은 사용자 경험을 크게 향상시킬 수 있습니다.

</blockquote></details>
<details>
<summary>lib/community/data/dto/member_dto.dart (1)</summary><blockquote>

`6-20`: **DTO 클래스 구현이 잘 되었습니다!**

MemberDto 클래스가 `freezed` 패키지를 사용하여 깔끔하게 구현되었네요. 몇 가지 개선할 수 있는 부분을 알려드립니다:

1. 모든 필드가 null 가능한(nullable) 타입으로 설정되어 있는데, 어떤 상황에서 null이 될 수 있는지 주석으로 설명해주면 다른 개발자들이 이해하기 쉬울 것 같습니다.

2. `fromJson` 팩토리 메서드를 사용한 것은 좋은 패턴입니다. 추가로 `toJson` 메서드도 자주 사용되니 확인해보세요. (freezed가 자동으로 생성해주므로 문제는 없습니다)

3. 실제 Member 모델이 auth 모듈에서 교체될 예정이라는 주석이 있는데, 이후 마이그레이션 계획이나 호환성 유지 방법에 대한 간단한 설명이 있으면 더 좋을 것 같습니다.


다음과 같이 필드에 대한 간단한 설명을 추가하는 것을 고려해보세요:

```diff
/// 실제 Member 모델은 auth 모듈에서 교체 예정!
+/// 이 DTO는 임시로 사용되며, auth 모듈의 Member 모델과 필드 구조를 맞추고 있습니다.
@freezed
abstract class MemberDto with _$MemberDto {
  const factory MemberDto({
+    /// 회원 고유 식별자
    String? id,
+    /// 회원 이메일 주소
    String? email,
+    /// 회원 닉네임
    String? nickname,
+    /// Firebase 인증 UID
    String? uid,
+    /// 온라인 상태 여부
    bool?   onAir,
+    /// 프로필 이미지 URL
    String? image,
  }) = _MemberDto;
```

</blockquote></details>
<details>
<summary>lib/community/domain/usecase/load_post_list_use_case.dart (1)</summary><blockquote>

`6-10`: **유스케이스 구현이 간결하고 좋습니다!**

클린 아키텍처 패턴을 잘 따르고 있으며, 의존성 주입(Dependency Injection)을 통해 리포지토리를 받아 사용하는 구조가 좋습니다. 다음 개선사항을 고려해보세요:

1. 클래스와 메서드에 대한 문서화 주석(documentation comments)을 추가하면 이 유스케이스의 목적과 사용법을 더 명확하게 이해할 수 있을 것입니다.

2. 현재는 페이지네이션(pagination) 기능이 없는데, 게시글 목록이 많아질 경우를 대비해 페이지 번호나 크기 매개변수를 추가하는 것을 고려해볼 수 있습니다.


다음과 같이 문서화 주석과 페이지네이션 기능을 추가하는 것을 고려해보세요:

```diff
+/// 게시글 목록을 불러오는 유스케이스
+/// 
+/// 커뮤니티의 게시글 목록을 리포지토리를 통해 불러오는 역할을 합니다.
class LoadPostListUseCase {
  const LoadPostListUseCase({required PostRepository repo}) : _repo = repo;
  final PostRepository _repo;

-  Future<Result<List<Post>>> execute() => _repo.loadPostList();
+  /// 게시글 목록을 불러옵니다.
+  /// 
+  /// [page]: 불러올 페이지 번호 (0부터 시작)
+  /// [size]: 한 페이지에 불러올 게시글 수
+  /// 
+  /// 반환값: 게시글 목록을 포함한 [Result] 객체
+  Future<Result<List<Post>>> execute({int page = 0, int size = 20}) => 
+      _repo.loadPostList(page: page, size: size);
}
```

</blockquote></details>
<details>
<summary>lib/community/module/community_router.dart (1)</summary><blockquote>

`5-11`: **라우터 설정이 명확합니다!**

라우팅 구조가 `go_router` 패키지를 활용하여 깔끔하게 구현되었습니다. 다음 개선사항을 고려해보세요:

1. 라우트 경로를 하드코딩하는 대신 상수로 분리하면 코드 전체에서 일관성을 유지하기 쉽고, 경로 변경 시 한 곳만 수정하면 되어 유지보수가 편리해집니다.

2. 향후 구현 예정인 라우트들을 TODO 주석으로 추가하여 개발 계획을 명확히 하면 좋을 것 같습니다.


다음과 같이 경로 상수와 TODO 주석을 추가하는 것을 고려해보세요:

```diff
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../presentation/community_list/community_list_screen_root.dart';

+/// 커뮤니티 모듈 라우트 경로 상수
+class CommunityRoutes {
+  static const String main = '/community';
+  static const String detail = '/community/:postId';
+  static const String search = '/community/search';
+  static const String write = '/community/write';
+}

final communityRoutes = [
  GoRoute(
-    path: '/community',
+    path: CommunityRoutes.main,
    builder: (context, state) => const CommunityListScreenRoot(),
  ),
  // 추가 상세/검색/글쓰기 경로는 이후 구현
+  // TODO: 상세 화면 라우트 구현
+  // TODO: 검색 화면 라우트 구현
+  // TODO: 글쓰기 화면 라우트 구현
];

final communityRouterProvider = Provider((ref) {
-  return GoRouter(initialLocation: '/community', routes: communityRoutes);
+  return GoRouter(initialLocation: CommunityRoutes.main, routes: communityRoutes);
});
```

</blockquote></details>
<details>
<summary>lib/community/presentation/community_list/community_list_screen_root.dart (1)</summary><blockquote>

`9-33`: **화면 구조와 액션 처리가 잘 구현되었습니다!**

Riverpod과 `ConsumerWidget`을 사용하여 상태 관리를 효과적으로 구현하셨네요. 패턴 매칭을 활용한 액션 처리 방식도 깔끔합니다. 다음 개선사항을 고려해보세요:

1. 네비게이션 경로가 하드코딩되어 있어 경로가 변경될 경우 여러 곳을 수정해야 합니다. 라우터 파일에서 정의한 상수를 사용하면 일관성을 유지하기 쉽습니다.

2. 네비게이션 결과를 처리하는 코드가 없습니다. 예를 들어 글쓰기 화면에서 돌아왔을 때 목록을 새로고침하는 기능이 필요할 수 있습니다.

3. 현재는 라우팅 로직이 위젯 내에 포함되어 있는데, 이를 별도의 네비게이션 서비스나 헬퍼로 분리하면 테스트와 유지보수가 더 쉬워집니다.


다음과 같이 라우트 상수를 사용하고 네비게이션 결과를 처리하는 코드를 추가해보세요:

```diff
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

import 'community_list_action.dart';
import 'community_list_notifier.dart';
import 'community_list_screen.dart';
+import '../../module/community_router.dart'; // 라우트 상수 import

class CommunityListScreenRoot extends ConsumerWidget {
  const CommunityListScreenRoot({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(communityListNotifierProvider);
    final notifier = ref.watch(communityListNotifierProvider.notifier);

    return CommunityListScreen(
      state: state,
      onAction: (action) async {
        switch (action) {
          case TapPost(:final postId):
-            context.push('/community/$postId');                             // 상세 화면 경로 예시
+            // 상세 화면으로 이동 후 돌아왔을 때 처리
+            final result = await context.push<bool?>(
+              '${CommunityRoutes.main}/$postId'
+            );
+            // 상세 화면에서 변경사항이 있었다면 목록 새로고침
+            if (result == true) {
+              await notifier.refreshPostList();
+            }
          case TapSearch():
-            context.push('/community/search');
+            await context.push(CommunityRoutes.search);
          case TapWrite():
-            context.push('/community/write');
+            // 글쓰기 화면에서 돌아왔을 때 목록 새로고침
+            final result = await context.push<bool?>(CommunityRoutes.write);
+            if (result == true) {
+              await notifier.refreshPostList();
+            }
          default:
            await notifier.onAction(action);
        }
      },
    );
  }
}
```

참고: 위 코드에서 `notifier.refreshPostList()` 메서드는 예시로, 이 메서드를 구현하거나 기존의 메서드를 활용하셔야 합니다.

</blockquote></details>
<details>
<summary>lib/community/community_main.dart (1)</summary><blockquote>

`15-19`: **테마 설정을 더 구체적으로 해주세요**

현재 `ThemeData()`는 비어 있어 기본 머티리얼 테마만 적용됩니다. 앱의 일관된 디자인을 위해 색상, 폰트, 텍스트 스타일 등이 포함된 커스텀 테마를 정의하는 것이 좋습니다. 이렇게 하면 앱 전체에서 일관된 UI를 제공할 수 있습니다.

예를 들어 다음과 같이 테마를 확장할 수 있습니다:

```diff
- theme: ThemeData(),
+ theme: ThemeData(
+   primaryColor: Colors.blue,
+   colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
+   useMaterial3: true,
+   // 추가적인 테마 속성들...
+ ),
```

</blockquote></details>
<details>
<summary>lib/community/domain/model/post.dart (1)</summary><blockquote>

`8-10`: **불필요한 공백 줄과 문서화 개선 제안**

도메인 모델에 불필요한 공백 줄들이 있습니다. 또한 클래스의 목적과 역할을 설명하는 문서화 주석을 추가하면 다른 개발자들이 코드를 이해하는 데 도움이 될 것입니다.

```diff
- 


+ /// Post 도메인 모델은 커뮤니티 게시글을 나타내는 불변 객체입니다.
+ /// 게시글의 기본 정보와 작성자, 해시태그, 좋아요 정보를 포함합니다.
```

</blockquote></details>
<details>
<summary>lib/community/data/mapper/post_mapper.dart (5)</summary><blockquote>

`2-2`: **사용하지 않는 주석 처리된 import 제거 필요**

사용하지 않는 주석 처리된 import 문이 있습니다. 코드 가독성을 위해 제거하는 것이 좋습니다.

```diff
- // import 'package:auth/domain/model/member.dart';
```

---

`1-12`: **문서화 추가 제안**

이 파일의 목적과 역할을 설명하는 간단한 문서화 주석을 추가하면 도움이 될 것입니다. 데이터 매퍼는 아키텍처의 중요한 부분이므로 그 목적을 명확히 하는 것이 좋습니다.

```diff
// lib/community/data/mapper/post_mapper.dart
+ /// DTO(Data Transfer Object)를 도메인 모델로 변환하기 위한 확장 메서드들을 제공합니다.
+ /// 이 매퍼들은 데이터 계층과 도메인 계층 사이의 변환을 담당합니다.
```

---

`27-36`: **익명 확장 메서드에 이름 추가 제안**

익명 확장 메서드에 이름을 추가하면 코드 가독성과 참조가 더 용이해집니다. 이는 나중에 다른 파일에서 이 확장을 사용하거나 참조할 때 특히 유용합니다.

```diff
- extension on MemberDto {
+ extension MemberDtoMapper on MemberDto {
```

---

`38-40`: **익명 확장 메서드에 이름 추가 제안**

이 확장 메서드에도 이름을 추가하면 좋겠습니다.

```diff
- extension on HashTagDto {
+ extension HashTagDtoMapper on HashTagDto {
```

---

`42-44`: **익명 확장 메서드에 이름 추가 제안**

이 확장 메서드에도 이름을 추가하면 좋겠습니다.

```diff
- extension on LikeDto {
+ extension LikeDtoMapper on LikeDto {
```

</blockquote></details>
<details>
<summary>lib/community/presentation/community_list/community_list_action.dart (1)</summary><blockquote>

`5-12`: **문서화 추가 제안**

이 sealed class의 목적과 각 액션의 의미를 설명하는 문서화 주석을 추가하면 다른 개발자들이 코드를 이해하는 데 도움이 될 것입니다.

```diff
+ /// 커뮤니티 목록 화면에서 발생할 수 있는 사용자 액션을 정의합니다.
+ /// 이 액션들은 UI 이벤트를 표현하며 상태 관리에 사용됩니다.
@freezed
sealed class CommunityListAction with _$CommunityListAction {
+ /// 목록을 새로고침합니다.
  const factory CommunityListAction.refresh() = Refresh;
+ /// 탭을 변경합니다.
  const factory CommunityListAction.changeTab(CommunityTabType tab) = ChangeTab;
+ /// 특정 게시물을 탭합니다.
  const factory CommunityListAction.tapPost(String postId) = TapPost;
+ /// 검색 기능을 탭합니다.
  const factory CommunityListAction.tapSearch() = TapSearch;
+ /// 글쓰기 기능을 탭합니다.
  const factory CommunityListAction.tapWrite() = TapWrite;
}
```

freezed와 sealed class를 사용한 것은 매우 좋은 접근 방식입니다. 이렇게 하면 타입 안전성이 보장되고 모든 가능한 액션을 명시적으로 처리할 수 있습니다.

</blockquote></details>
<details>
<summary>lib/community/module/community_di.dart (3)</summary><blockquote>

`1-5`: **import 구문 정리 제안**

import 구문을 층별로 그룹화하면 코드의 구조와 의존성을 더 명확하게 볼 수 있습니다.

```diff
+ // 외부 패키지
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

+ // 데이터 소스
import '../data/data_source/mock_post_data_source_impl.dart';
import '../data/data_source/post_data_source.dart';

+ // 리포지토리
import '../data/repository_impl/post_repository_impl.dart';
import '../domain/repository/post_repository.dart';

+ // 유스케이스
import '../domain/usecase/load_post_list_use_case.dart';
import '../domain/usecase/switch_tab_use_case.dart';
```

---

`13-14`: **데이터 소스 Provider 이름 명확화 제안**

현재 Provider 이름은 실제 구현이 Mock 데이터를 사용한다는 것을 명시하지 않습니다. 개발 중에는 이해할 수 있지만, 프로덕션 코드와 혼동을 방지하기 위해 이름을 더 명확히 하는 것이 좋습니다.

```diff
@riverpod
- PostDataSource postDataSource(Ref ref) => PostDataSourceImpl();
+ PostDataSource mockPostDataSource(Ref ref) => PostDataSourceImpl();
```

그에 따라 다른 참조도 업데이트해야 합니다:

```diff
@riverpod
PostRepository postRepository(Ref ref) =>
-    PostRepositoryImpl(dataSource: ref.watch(postDataSourceProvider));
+    PostRepositoryImpl(dataSource: ref.watch(mockPostDataSourceProvider));
```

---

`11-25`: **문서화 추가 제안**

의존성 주입(DI) 모듈은 애플리케이션의 중요한 부분이므로, 각 Provider의 목적과 역할을 설명하는 문서화를 추가하면 좋겠습니다.

```diff
part 'community_di.g.dart';

+ /// 커뮤니티 기능의 의존성 주입을 설정하는 모듈입니다.
+ /// 각 계층별로 필요한 객체들의 인스턴스를 제공합니다.

+ /// 게시물 데이터 소스를 제공합니다.
+ /// 현재는 개발 및 테스트 목적으로 Mock 구현체를 사용합니다.
@riverpod
PostDataSource postDataSource(Ref ref) => PostDataSourceImpl();

+ /// 게시물 리포지토리를 제공합니다.
+ /// 데이터 소스로부터 데이터를 가져와 도메인 모델로 변환합니다.
@riverpod
PostRepository postRepository(Ref ref) =>
    PostRepositoryImpl(dataSource: ref.watch(postDataSourceProvider));

+ /// 게시물 목록을 불러오는 유스케이스를 제공합니다.
@riverpod
LoadPostListUseCase loadPostListUseCase(Ref ref) =>
    LoadPostListUseCase(repo: ref.watch(postRepositoryProvider));

+ /// 탭 전환을 처리하는 유스케이스를 제공합니다.
@riverpod
SwitchTabUseCase switchTabUseCase(Ref ref) => SwitchTabUseCase();
```

전체적인 의존성 주입 설계는 잘 되어 있습니다. 각 계층(데이터 소스, 리포지토리, 유스케이스)이 적절히 분리되어 있고, Riverpod을 통해 깔끔하게 관리되고 있습니다. 👍

</blockquote></details>
<details>
<summary>lib/community/presentation/community_list/community_list_notifier.dart (1)</summary><blockquote>

`58-67`: **정렬 로직이 간결하고 명확합니다.**

탭 타입에 따라 게시글을 인기순(좋아요 수) 또는 최신순(생성일)으로 정렬하는 로직이 잘 구현되어 있습니다. 스프레드 연산자(`[...list]`)를 사용하여 원본 리스트를 변경하지 않고 복사본을 정렬한 점도 좋습니다.

다만, 각 정렬 케이스에서 정렬 후 바로 반환하는 방식보다는 더 간결하게 작성할 수 있습니다.

다음과 같이 조금 더 간결하게 작성할 수 있습니다:

```diff
List<Post> _applySort(List<Post> list, CommunityTabType tab) {
  switch (tab) {
    case CommunityTabType.popular:
-      final sorted = [...list]..sort((a, b) => b.like.length.compareTo(a.like.length));
-      return sorted;
+      return [...list]..sort((a, b) => b.like.length.compareTo(a.like.length));
    case CommunityTabType.newest:
-      final sorted = [...list]..sort((a, b) => b.createdAt.compareTo(a.createdAt));
-      return sorted;
+      return [...list]..sort((a, b) => b.createdAt.compareTo(a.createdAt));
  }
}
```

</blockquote></details>
<details>
<summary>lib/community/presentation/community_list/community_list_screen.dart (2)</summary><blockquote>

`54-65`: **탭바 구현이 깔끔합니다.**

탭바 구현이 간결하고 목적에 맞게 잘 설계되어 있습니다. 탭 선택 시 적절한 액션을 발생시키는 방식도 좋습니다.

탭 인덱스를 직접 사용하여 탭 타입을 결정하는 방식이 간단하지만, 향후 탭 순서가 변경되거나 탭이 추가될 경우 문제가 발생할 수 있습니다.

더 안전한 접근법은 탭 정보를 구조화하여 관리하는 것입니다:

```dart
// 더 확장성 있는, 데이터 기반 탭 정의의 예시
final tabs = [
  (text: '인기순', type: CommunityTabType.popular),
  (text: '최신순', type: CommunityTabType.newest),
];

Widget _buildTabBar() => TabBar(
  // ... 기존 속성들 ...
  onTap: (i) => onAction(CommunityListAction.changeTab(tabs[i].type)),
  tabs: tabs.map((tab) => Tab(text: tab.text)).toList(),
);
```

이렇게 하면 탭 정보가 한 곳에서 관리되어 변경 사항을 더 쉽게 적용할 수 있습니다.

---

`67-89`: **상태 처리 로직이 잘 구현되어 있습니다.**

비동기 상태(로딩, 오류, 데이터)에 따른 UI 처리가 명확하게 구현되어 있습니다. 특히 패턴 매칭을 사용하여 각 상태에 맞는 위젯을 반환하는 방식이 코드의 가독성을 높입니다.

몇 가지 개선 사항이 있습니다:


1. 74번 줄의 `final list = value.isNotEmpty ? value : [];` 코드는 불필요합니다. value가 비어있으면 빈 리스트를, 그렇지 않으면 value를 반환하는 조건문인데, 이는 그냥 value를 사용하는 것과 동일합니다.

```diff
- final list = value.isNotEmpty ? value : [];
+ final list = value;
```

2. 중복 코드를 제거할 수 있습니다. 45번 줄과 76번 줄에서 동일한 메시지("등록된 게시글이 없습니다")를 표시하고 있습니다.

```diff
Expanded(
-  child: _buildPostList() ?? const Center(child: Text('등록된 게시글이 없습니다')),
+  child: _buildPostList(),
),
```

그리고 _buildPostList 메서드에서 빈 리스트 처리를 통합할 수 있습니다.

3. 메서드 마지막의 `return null;`은 모든 케이스가 switch에서 처리되므로 필요하지 않습니다.

</blockquote></details>
<details>
<summary>lib/community/module/util/community_tab_type_enum.dart (1)</summary><blockquote>

`1-1`: **문서화 주석 추가 제안**  
초보자도 enum 값을 볼 때 탭의 용도를 한눈에 이해할 수 있도록 아래와 같이 doc comment를 추가해보세요:

```dart
/// 인기 게시글 정렬 탭
popular,
/// 최신 게시글 정렬 탭
newest,
```

이렇게 하면 유지보수 과정에서 코드 이해가 훨씬 수월해집니다!

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 7e9d454bf8e983d26ce9ce1af05fe140cae0b907 and d77804d281704ad3e12da1f083719d304fe36c8d.

</details>

<details>
<summary>📒 Files selected for processing (28)</summary>

* `lib/community/community.md` (1 hunks)
* `lib/community/community_main.dart` (1 hunks)
* `lib/community/data/data_source/mock_post_data_source_impl.dart` (1 hunks)
* `lib/community/data/data_source/post_data_source.dart` (1 hunks)
* `lib/community/data/dto/comment_dto.dart` (1 hunks)
* `lib/community/data/dto/hash_tag_dto.dart` (1 hunks)
* `lib/community/data/dto/like_dto.dart` (1 hunks)
* `lib/community/data/dto/member_dto.dart` (1 hunks)
* `lib/community/data/dto/post_dto.dart` (1 hunks)
* `lib/community/data/mapper/post_mapper.dart` (1 hunks)
* `lib/community/data/repository_impl/post_repository_impl.dart` (1 hunks)
* `lib/community/domain/model/comment.dart` (1 hunks)
* `lib/community/domain/model/hash_tag.dart` (1 hunks)
* `lib/community/domain/model/like.dart` (1 hunks)
* `lib/community/domain/model/post.dart` (1 hunks)
* `lib/community/domain/repository/post_repository.dart` (1 hunks)
* `lib/community/domain/usecase/load_post_list_use_case.dart` (1 hunks)
* `lib/community/domain/usecase/switch_tab_use_case.dart` (1 hunks)
* `lib/community/module/community_di.dart` (1 hunks)
* `lib/community/module/community_router.dart` (1 hunks)
* `lib/community/module/util/community_tab_type_enum.dart` (1 hunks)
* `lib/community/module/util/board_type_enum.dart` (1 hunks)
* `lib/community/presentation/community_list/community_list_action.dart` (1 hunks)
* `lib/community/presentation/community_list/community_list_notifier.dart` (1 hunks)
* `lib/community/presentation/community_list/community_list_screen.dart` (1 hunks)
* `lib/community/presentation/community_list/community_list_screen_root.dart` (1 hunks)
* `lib/community/presentation/community_list/community_list_state.dart` (1 hunks)
* `lib/community/presentation/component/post_list_item.dart` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (10)</summary><blockquote>

<details>
<summary>lib/community/data/dto/post_dto.dart (1)</summary>

`12-28`: **코드가 잘 구성되어 있습니다!**

DTO(Data Transfer Object) 클래스가 깔끔하게 정의되어 있고, 다음과 같은 좋은 점들이 있습니다:

1. 주석을 통해 모든 필드가 nullable임을 명확히 설명하고 있습니다.
2. `MemberDto`가 인증 기능 완성 전까지 임시로 사용된다는 주석이 포함되어 있어 다른 개발자들이 이해하기 쉽습니다.
3. `freezed` 패키지를 활용하여 불변(immutable) 객체를 생성하고 있습니다.
4. JSON 직렬화/역직렬화를 위한 팩토리 메서드가 잘 구현되어 있습니다.

초보자를 위한 설명: 
- DTO는 계층 간(예: API와 애플리케이션) 데이터를 주고받을 때 사용하는 객체입니다.
- `freezed`는 Dart에서 불변 객체를 쉽게 만들 수 있게 해주는 패키지로, 상태 관리와 테스트에 유리합니다.
- nullable 필드(`String?`처럼 끝에 ?가 붙은 것)는 해당 값이 null일 수 있음을 의미합니다.

</details>
<details>
<summary>lib/community/domain/model/post.dart (1)</summary>

`12-24`: **잘 설계된 도메인 모델입니다!**

Freezed 패키지를 활용하여 불변(immutable) 객체로 만든 점이 좋습니다. 불변 객체는 예측 가능한 상태 관리에 도움이 됩니다. 필수 필드와 선택적 필드(기본값이 있는 필드)를 명확하게 구분한 설계도 좋습니다.

몇 가지 참고사항:
- `abstract class`와 `const factory`의 조합은 Freezed의 일반적인 패턴이지만, 이 패턴에 익숙하지 않은 개발자를 위해 간단한 주석으로 설명하면 좋을 것 같습니다.
- 향후 필드가 추가될 경우 유지보수성을 위해 각 필드에 간단한 설명 주석을 추가하는 것도 고려해보세요.

</details>
<details>
<summary>lib/community/data/mapper/post_mapper.dart (1)</summary>

`13-25`: **null 처리가 잘 되어 있습니다!**

DTO에서 도메인 모델로 변환할 때 null 값을 적절하게 처리하고 있어 좋습니다. 모든 필드에 대해 기본값을 제공하여 도메인 모델이 항상 유효한 상태를 유지하도록 하는 방식이 안전합니다.

앱이 더 복잡해지면 이러한 매핑 로직을 helper 함수로 추출하는 것을 고려해볼 수 있습니다. 예를 들어 `String? nullableString -> String nonNullString` 같은 공통 변환 패턴을 위한 헬퍼 함수를 만들면 코드 중복을 줄일 수 있습니다.

</details>
<details>
<summary>lib/community/data/data_source/mock_post_data_source_impl.dart (1)</summary>

`1-44`: **모형 데이터 구현이 잘 되어있습니다.**

코드가 요구사항에 맞게 게시글 목록을 위한 모형 데이터를 잘 생성하고 있습니다. 각 게시글마다 고유한 ID, 제목, 내용, 작성자 정보, 게시판 유형, 생성 시간, 해시태그, 좋아요 등의 정보를 포함하고 있어 실제 데이터와 유사한 환경에서 테스트할 수 있습니다.

```dart
// 모형 데이터 생성 예시
return PostDto(
  id: 'post_$i',
  title: '목 게시글 $i',
  content: '[Mock] 내용 $i',
  member: MemberDto(...),
  boardType: BoardType.free,
  createdAt: DateTime.now().subtract(Duration(minutes: i * 5)),
  hashTag: [...],
  like: [...],
);
```

실제 백엔드 연동 시 쉽게 전환할 수 있도록 인터페이스를 준수하여 구현되어 있습니다.

</details>
<details>
<summary>lib/community/presentation/community_list/community_list_notifier.dart (3)</summary>

`15-24`: **상태관리 구현이 잘 되어 있습니다.**

`CommunityListNotifier` 클래스가 요구사항에 맞게 `AsyncValue`를 다루기 위해 `asyncNotifier` 대신 일반 `notifier`를 사용하여 구현되었습니다. 초기 상태 설정과 비동기 로딩 처리가 적절히 구현되어 있습니다.

상태 초기화 시 `Future.microtask()`를 사용하여 현재 실행 주기가 완료된 후 데이터를 가져오도록 한 부분이 좋습니다. 이는 상태 초기화와 데이터 로딩 사이의 타이밍 문제를 방지하는 좋은 방법입니다.

---

`27-37`: **비동기 데이터 로딩 및 오류 처리가 훌륭합니다.**

`_fetch` 메서드는 비동기 데이터 로딩을 위한 상태 변화를 명확하게 관리하고 있습니다:
1. 로딩 상태로 변경
2. 데이터 가져오기 시도
3. 결과에 따라 성공 또는 오류 상태로 업데이트

특히 `Result` 패턴을 사용하여 성공과 오류 케이스를 명확하게 구분하고, 오류 발생 시 스택 트레이스를 포함시켜 디버깅에 도움이 되도록 한 점이 좋습니다.

---

`39-56`: **액션 처리 로직이 명확합니다.**

사용자 액션 처리 로직이 `switch` 문을 활용하여 각 액션 타입별로 명확하게 구분되어 있습니다. 특히 탭 변경 시 전체 데이터를 다시 불러오지 않고 기존 데이터를 재정렬하는 방식은 성능 최적화 측면에서 좋은 접근법입니다.

`maybeWhen` 메서드를 활용하여 데이터가 있는 경우에만 정렬을 적용하고, 그렇지 않은 경우 기존 상태를 유지하는 방식도 안전하게 구현되어 있습니다.

</details>
<details>
<summary>lib/community/community.md (1)</summary>

`1-44`: **요구사항이 명확하게 작성되어 있습니다.**

요구사항 문서가 구현해야 할 기능과 구조에 대해 상세하게 설명되어 있습니다. 이 문서는 개발 과정에서 참고할 수 있는 좋은 가이드라인이 됩니다.

주요 요구사항들:
1. 데이터 소스에서 정렬 파라미터 없이 모든 게시글 목록 제공
2. freezed 3.0.6을 이용한 DTO 작성
3. ID 타입을 int에서 String으로 변경
4. 필수 필드값 정의 (id, title, content, member 등)
5. 명명된 매개변수를 사용한 의존성 주입
6. 상태 관리에 notifier 사용
7. DTO는 nullable 구현
8. 임시 Member 모델 사용
9. 엔티티별 필드 정의
10. 절대 경로를 사용한 import
11. 파일 구조 정리 방법

이 요구사항들이 실제 구현에 잘 반영되어 있어 개발 일관성과 품질 향상에 도움이 됩니다.

</details>
<details>
<summary>lib/community/presentation/community_list/community_list_screen.dart (1)</summary>

`9-52`: **화면 구조가 잘 설계되어 있습니다.**

커뮤니티 목록 화면이 상태와 액션 처리를 위한 콜백을 통해 깔끔하게 설계되어 있습니다. UI와 비즈니스 로직이 분리되어 있어 코드의 유지보수와 테스트가 용이합니다.

주요 UI 구성 요소:
- 앱바와 검색 버튼
- 인기순/최신순 탭
- 새 글 작성을 위한 플로팅 액션 버튼
- 당겨서 새로고침 기능
- 게시글 목록 또는 빈 상태 메시지

이러한 구조는 사용자에게 직관적인 인터페이스를 제공합니다.

</details>
<details>
<summary>lib/community/module/util/community_tab_type_enum.dart (1)</summary>

`1-1`: **[좋은 네이밍과 컨벤션 준수]**  
`CommunityTabType` enum과 `popular`, `newest` 값이 Dart 스타일 가이드에 잘 맞춰져 있어 내부 로직의 가독성이 뛰어납니다. 👏

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-02T06:27:44Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 8**

<details>
<summary>🧹 Nitpick comments (22)</summary><blockquote>

<details>
<summary>lib/community/module/util/board_type_enum.dart (1)</summary><blockquote>

`1-1`: **게시판 타입 열거형 구현이 명확하게 되어 있습니다!**

`enum`은 관련된 상수 값들을 그룹화하는 데 사용되는 특별한 클래스입니다. 여기서는 게시판의 종류를 `notice`(공지사항), `free`(자유게시판), `qna`(질문과 답변) 세 가지로 분류하고 있습니다. 

이런 방식으로 구현하면 타입 안전성이 확보되어 오류를 줄일 수 있습니다. 예를 들어, 문자열을 직접 사용하면 "notice"를 "noitce"로 오타를 낼 수 있지만, 열거형을 사용하면 이런 실수를 컴파일 시점에 잡아낼 수 있습니다.

개선을 위한 제안:
1. 각 게시판 타입에 대한 설명을 주석으로 추가하면 더 명확할 것 같습니다.
2. 나중에 번역이나 화면에 표시할 때 사용할 수 있는 표시 이름을 getter로 추가하는 것도 고려해보세요.

```dart
/// 게시판 타입을 정의하는 열거형
enum BoardType {
  notice, // 공지사항
  free,   // 자유게시판
  qna     // 질문과 답변
}

// 확장 기능 예시
extension BoardTypeExtension on BoardType {
  String get displayName {
    switch (this) {
      case BoardType.notice:
        return '공지사항';
      case BoardType.free:
        return '자유게시판';
      case BoardType.qna:
        return 'Q&A';
    }
  }
}
```

</blockquote></details>
<details>
<summary>lib/community/data/data_source/post_data_source.dart (1)</summary><blockquote>

`1-7`: **데이터 소스 인터페이스가 명확하게 정의되어 있습니다!**

이 파일은 게시글 데이터를 가져오기 위한 추상 인터페이스를 정의하고 있습니다. `abstract interface class`는 Dart 언어에서 인터페이스 역할을 하는 클래스를 명시적으로 선언하는 방법입니다.

`PostDataSource` 인터페이스는 `fetchPostList()` 메소드 하나만 가지고 있으며, 이는 모든 게시글 목록을 비동기적으로 가져오는 기능을 정의합니다. `Future<List<PostDto>>`는 이 메소드가 비동기적으로 `PostDto` 객체들의 리스트를 반환한다는 의미입니다.

이렇게 인터페이스를 정의함으로써 다양한 구현체(실제 API 호출, 로컬 데이터베이스 접근, 모의 데이터 등)를 쉽게 교체할 수 있는 유연한 구조가 만들어집니다.

개선을 위한 제안:
1. 게시글을 필터링하거나 페이지네이션을 지원하는 매개변수를 추가하는 것이 좋을 것 같습니다.
2. 게시글 CRUD(생성, 읽기, 업데이트, 삭제) 작업을 위한 추가 메소드도 고려해보세요.
3. 오류 처리를 위한 구체적인 예외 유형을 정의하면 더 견고한 코드가 될 것입니다.

```dart
abstract interface class PostDataSource {
  /// 모든 게시글을 한 번에 가져온다.
  Future<List<PostDto>> fetchPostList();
  
  /// 제안: 페이지네이션과 필터링을 지원하는 메소드 추가
  Future<List<PostDto>> fetchPostListWithParams({
    int? page,
    int? limit,
    BoardType? boardType,
    String? searchQuery,
  });
  
  /// 제안: 게시글 생성 메소드
  Future<PostDto> createPost(PostDto post);
  
  /// 제안: 게시글 상세 조회 메소드
  Future<PostDto> fetchPostById(String postId);
  
  /// 제안: 게시글 업데이트 메소드
  Future<PostDto> updatePost(PostDto post);
  
  /// 제안: 게시글 삭제 메소드
  Future<bool> deletePost(String postId);
}
```

</blockquote></details>
<details>
<summary>lib/community/domain/model/like.dart (1)</summary><blockquote>

`1-11`: **좋아요 도메인 모델이 잘 구현되어 있습니다!**

이 파일은 Freezed 패키지를 사용하여 불변(immutable) 데이터 모델인 `Like` 클래스를 정의하고 있습니다. Freezed는 Dart 생태계에서 널리 사용되는 코드 생성 라이브러리로, 불변 객체 생성을 간소화합니다.

`@freezed` 어노테이션은 Freezed 라이브러리에게 이 클래스에 대한 코드를 생성하라고 지시합니다. `part 'like.freezed.dart';`는 생성된 코드가 저장될 파일을 지정합니다.

`Like` 클래스는 `boardId`와 `memberId` 두 개의 필수 문자열 필드를 가지고 있으며, 각각 게시글 ID와 사용자 ID를 나타냅니다. 이렇게 하면 어떤 사용자가 어떤 게시글에 좋아요를 눌렀는지 추적할 수 있습니다.

개선을 위한 제안:
1. 타임스탬프 필드를 추가하여 좋아요가 눌린 시간을 기록하면 유용할 수 있습니다.
2. 간단한 설명을 주석으로 추가하면 다른 개발자들이 코드를 이해하는 데 도움이 될 것입니다.
3. ID 유효성 검사 로직을 추가하는 것도 고려해보세요.

```dart
/// 사용자가 게시글에 누른 좋아요를 나타내는 도메인 모델
@freezed
abstract class Like with _$Like {
  const factory Like({
    required String boardId,
    required String memberId,
    DateTime? createdAt, // 좋아요 생성 시간
  }) = _Like;
  
  /// 제안: 유효성 검사를 위한 팩토리 메소드
  factory Like.create({
    required String boardId,
    required String memberId,
  }) {
    // ID 형식 유효성 검사 등을 여기서 수행할 수 있습니다
    return Like(
      boardId: boardId,
      memberId: memberId,
      createdAt: DateTime.now(),
    );
  }
}
```

</blockquote></details>
<details>
<summary>lib/community/domain/usecase/switch_tab_use_case.dart (1)</summary><blockquote>

`3-5`: **UseCase 구현이 단순 리턴만 하고 있습니다.**

현재 `SwitchTabUseCase`의 `execute` 메서드는 입력받은 `next` 매개변수를 그대로 반환하고 있습니다. 이는 불필요한 추상화로 보일 수 있습니다.

비즈니스 로직이 필요한 경우 UseCase를 사용하는 것이 좋은데, 현재는 단순히 값을 전달만 하고 있습니다. 이 UsaCase가 필요한 이유를 고려해보세요:

1. 향후 탭 전환에 추가 로직이 필요할 예정인가요?
2. 단순히 값을 전달하는 경우라면 UseCase 없이 직접 값을 사용하는 것도 가능합니다.

UseCase를 사용한다면, 탭 전환에 관련된 로직(예: 특정 조건에서 탭 전환 막기, 탭 전환 시 이벤트 발생 등)을 추가할 수 있습니다.

</blockquote></details>
<details>
<summary>lib/community/presentation/component/post_list_item.dart (1)</summary><blockquote>

`19-26`: **UI 개선을 위한 제안과 null 안전성 확보**

현재 구현은 기능적으로는 문제가 없지만, UI와 안전성 측면에서 개선할 점이 있습니다:

1. 좋아요 아이콘과 개수 표시 부분에 null 체크를 추가하는 것이 좋습니다.
2. 좋아요 수에 따라 아이콘 색상을 변경하면 사용자 경험이 향상될 수 있습니다.

```diff
trailing: Row(
  mainAxisSize: MainAxisSize.min,
  children: [
-   const Icon(Icons.favorite_border, size: 16),
+   Icon(
+     post.like.isNotEmpty ? Icons.favorite : Icons.favorite_border,
+     size: 16,
+     color: post.like.isNotEmpty ? Colors.red : null,
+   ),
    const SizedBox(width: 4),
-   Text('${post.like.length}'),
+   Text('${post.like.length}', style: const TextStyle(fontSize: 14)),
  ],
),
```

초보자를 위한 설명:
- `post.like.isNotEmpty`: 좋아요 목록이 비어있지 않은지 확인합니다.
- 좋아요가 있으면 채워진 하트 아이콘(`Icons.favorite`)을, 없으면 빈 하트 아이콘(`Icons.favorite_border`)을 표시합니다.
- 글꼴 크기를 조정해 가독성을 높입니다.

</blockquote></details>
<details>
<summary>lib/community/data/dto/member_dto.dart (1)</summary><blockquote>

`7-16`: **DTO 필드 정의와 nullable 처리**  
모든 필드를 nullable로 정의해 API 응답 누락에 대비한 점이 좋습니다. 추가로, 각 필드에 간단한 주석(설명)을 달아두면 초보자나 후속 개발자가 모델의 역할을 더 쉽게 이해할 수 있으니 고려해 보세요.

</blockquote></details>
<details>
<summary>lib/community/domain/usecase/load_post_list_use_case.dart (1)</summary><blockquote>

`6-11`: **깔끔하게 구현된 UseCase 패턴입니다!**

UseCase는 비즈니스 로직을 캡슐화하고 Repository와 Presentation 계층 사이의 중간 다리 역할을 합니다. 현재 구현은 단순하면서도 효과적이네요. 다만, 메소드와 클래스에 문서 주석(doc comments)을 추가하면 UseCase의 목적과 동작 방식을 다른 개발자들이 더 쉽게 이해할 수 있을 것 같습니다.

```dart
/// 게시물 목록을 불러오는 UseCase
/// 
/// Repository에서 게시물 목록을 가져와 도메인 모델 형태로 반환합니다.
class LoadPostListUseCase {
  const LoadPostListUseCase({required PostRepository repo}) : _repo = repo;
  final PostRepository _repo;

  /// 게시물 목록을 비동기적으로 로드합니다.
  /// 
  /// Returns: 게시물 목록이 담긴 Result 객체
  Future<Result<List<Post>>> execute() => _repo.loadPostList();
}
```

이런 문서화는 팀원들과의 협업과 코드 유지보수에 큰 도움이 됩니다.

</blockquote></details>
<details>
<summary>lib/community/module/community_router.dart (1)</summary><blockquote>

`5-15`: **라우팅 설정이 간결하게 정의되었네요!**

현재는 커뮤니티 목록 화면만 구현되어 있고, 추가 경로는 이후에 구현될 예정이라고 주석으로 잘 명시해 두셨습니다. 라우터 설정에 오류 처리 기능을 추가하면 더 견고해질 것 같습니다.

```dart
final communityRouterProvider = Provider((ref) {
  return GoRouter(
    initialLocation: '/community', 
    routes: communityRoutes,
    // 라우트 오류 처리
    errorBuilder: (context, state) => const RouteErrorScreen(),
  );
});
```

`RouteErrorScreen`은 경로를 찾을 수 없을 때 표시할 화면입니다. 이런 오류 처리는 사용자 경험을 향상시키는 데 도움이 됩니다. 또한 나중에 중첩 라우트(nested routes)를 추가할 때는 ShellRoute를 고려해 보세요. 탭 기반 내비게이션을 구현할 때 유용합니다.

</blockquote></details>
<details>
<summary>lib/community/community_main.dart (1)</summary><blockquote>

`5-21`: **커뮤니티 기능의 독립적인 진입점이 잘 구현되었습니다!**

이 파일은 커뮤니티 기능을 독립적으로 실행하고 테스트할 수 있는 진입점을 제공합니다. 이는 모듈식 개발 접근 방식에 적합합니다. 다만, 메인 앱에 통합될 때의 방식에 대한 설명이 있으면 도움이 될 것 같습니다.

테마 설정이 현재 비어 있는데, 앱의 디자인 시스템을 반영한 테마 설정을 추가하면 좋겠습니다:

```dart
theme: ThemeData(
  // 앱의 디자인 시스템을 반영한 테마 설정
  colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
  useMaterial3: true,
  // 커뮤니티 화면에 적합한 기타 테마 설정
),
```

또한 이 파일이 개발/테스트용인지, 아니면 메인 앱에 어떻게 통합되는지에 대한 주석을 추가하면 다른 개발자들이 이해하는 데 도움이 될 것입니다.

</blockquote></details>
<details>
<summary>lib/community/presentation/community_list/community_list_state.dart (1)</summary><blockquote>

`11-17`: **Freezed를 활용한 상태 클래스가 잘 설계되었습니다!**

Freezed 패키지를 사용하여 불변 상태를 정의하는 접근 방식이 좋습니다. 현재는 게시물 목록과 현재 탭이라는 두 가지 핵심 상태를 관리하고 있네요.

상태 관리를 더 향상시키기 위해 다음과 같은 추가 속성을 고려해 볼 수 있습니다:

```dart
@freezed
abstract class CommunityListState with _$CommunityListState {
  const factory CommunityListState({
    @Default(AsyncValue<List<Post>>.loading()) AsyncValue<List<Post>> postList,
    @Default(CommunityTabType.popular) CommunityTabType currentTab,
    // 추가 속성들
    @Default('') String searchQuery,
    @Default(false) bool isRefreshing,
    @Default(0) int currentPage,
    @Default(false) bool hasReachedEnd,
  }) = _CommunityListState;
}
```

이러한 추가 속성은 검색, 페이지네이션, 새로고침 상태 등을 관리하는 데 도움이 될 수 있습니다. 필요에 따라 선택적으로 추가해 보세요!

</blockquote></details>
<details>
<summary>lib/community/data/dto/post_dto.dart (1)</summary><blockquote>

`1-28`: **데이터 전송 객체(DTO) 설계가 잘 되어 있습니다!**

`PostDto` 클래스가 `freezed` 패키지를 활용하여 깔끔하게 설계되었습니다. 모든 필드가 nullable로 선언된 점이 DTO의 역할에 잘 맞습니다. 데이터베이스나 API에서 받아오는 정보가 항상 완전하지 않을 수 있기 때문이죠.

몇 가지 작은 개선 사항을 제안합니다:
1. 주석에 각 필드에 대한 간단한 설명을 추가하면 더 이해하기 쉬울 것 같습니다.
2. 19번 줄의 임시 DTO 주석은 나중에 리마인더로 TODO 형식으로 변경하는 것이 좋을 수 있습니다.

```diff
// 예시:
// 게시글 DTO (모든 필드 nullable)
@freezed
abstract class PostDto with _$PostDto {
  const factory PostDto({
    String? id,
    String? title,
    String? content,
-   MemberDto? member,               // 👉 auth 완성 전까지 임시 DTO
+   MemberDto? member,               // TODO: auth 완성 후 실제 회원 정보로 대체 필요
    BoardType? boardType,
    DateTime? createdAt,
    List<HashTagDto>? hashTag,
    List<LikeDto>? like,
  }) = _PostDto;
```

</blockquote></details>
<details>
<summary>lib/community/presentation/community_list/community_list_screen_root.dart (1)</summary><blockquote>

`1-33`: **화면 구조와 내비게이션 처리가 명확합니다!**

`CommunityListScreenRoot` 클래스가 Riverpod을 활용한 상태 관리와 내비게이션을 잘 구현했습니다. 특히 패턴 매칭을 사용한 액션 처리 방식이 코드를 읽기 쉽게 만들어 줍니다.

개선할 수 있는 부분:
1. 내비게이션 경로 상수화: 경로 문자열('/community/search' 등)을 상수로 분리하면 유지보수성이 향상됩니다.
2. 22번 줄의 주석은 실제 구현으로 대체하거나 TODO로 표시하는 것이 좋습니다.

```diff
+ // 경로 상수 추가 예시
+ class CommunityRoutes {
+   static const String detail = '/community/';
+   static const String search = '/community/search';
+   static const String write = '/community/write';
+ }

class CommunityListScreenRoot extends ConsumerWidget {
  // ...
  
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // ...
    
    return CommunityListScreen(
      state: state,
      onAction: (action) async {
        switch (action) {
          case TapPost(:final postId):
-           context.push('/community/$postId');                             // 상세 화면 경로 예시
+           context.push('${CommunityRoutes.detail}$postId');             // 상세 화면으로 이동
          case TapSearch():
-           context.push('/community/search');
+           context.push(CommunityRoutes.search);
          case TapWrite():
-           context.push('/community/write');
+           context.push(CommunityRoutes.write);
          default:
            await notifier.onAction(action);
        }
      },
    );
  }
}
```

</blockquote></details>
<details>
<summary>lib/community/domain/model/post.dart (1)</summary><blockquote>

`1-26`: **도메인 모델 설계가 명확하고 견고합니다!**

`Post` 클래스는 도메인 모델로서 필요한 모든 속성을 포함하고 있으며, `freezed`를 사용해 불변 객체로 만든 점이 좋습니다. 필수 필드와 기본값이 있는 선택적 필드가 잘 구분되어 있습니다.

문서화 측면에서 약간의 개선을 제안합니다:
1. 클래스와 주요 필드에 대한 문서 주석을 추가하면 코드를 처음 접하는 개발자도 쉽게 이해할 수 있을 것입니다.
2. 리스트 기본값 선언 부분의 형식을 일관되게 정렬하면 가독성이 더 좋아질 것 같습니다.

```diff
+ /// 게시글을 나타내는 도메인 모델입니다.
+ /// 모든 필수 필드는 null이 아닌 값을 가져야 합니다.
@freezed
abstract class Post with _$Post {
  const factory Post({
+   /// 게시글의 고유 식별자
    required String id,
    required String title,
    required String content,
    required Member member,
    required BoardType boardType,
    required DateTime createdAt,
-   @Default(<HashTag>[]) List<HashTag> hashTag,
-   @Default(<Like>[])    List<Like>    like,
+   @Default(<HashTag>[]) List<HashTag> hashTag,
+   @Default(<Like>[]) List<Like> like,
  }) = _Post;
}
```

</blockquote></details>
<details>
<summary>lib/community/data/mapper/post_mapper.dart (2)</summary><blockquote>

`2-2`: **주석 처리된 import 문 제거 고려하기**

주석 처리된 import 문이 있습니다. 코드 정리를 위해 사용하지 않는 import 문은 제거하는 것이 좋습니다. 주석 처리된 코드는 나중에 코드를 읽는 사람에게 혼란을 줄 수 있어요.

```diff
-// import 'package:auth/domain/model/member.dart';
```

---

`13-25`: **모델 변환 로직 구현이 잘 되었습니다!**

`PostDto`를 `Post` 도메인 모델로 변환하는 확장 메서드가 잘 구현되었습니다. null 체크와 기본값 설정을 통해 안전하게 데이터를 처리하고 있어요. 다만, 기본 Member 객체 생성 부분은 별도의 상수나 팩토리 메서드로 추출하면 코드의 가독성이 더 좋아질 수 있습니다.

```diff
 extension PostDtoMapper on PostDto {
   Post toModel() => Post(
         id: id ?? '',
         title: title ?? '',
         content: content ?? '',
-        member: member?.toModel() ??
-            Member(id: '', email: '', nickname: '', uid: '', onAir: false, image: ''),
+        member: member?.toModel() ?? _defaultMember,
         boardType: boardType ?? BoardType.free,
         createdAt: createdAt ?? DateTime.now(),
         hashTag: (hashTag ?? []).map((e) => e.toModel()).toList(),
         like: (like ?? []).map((e) => e.toModel()).toList(),
       );
 }

+// 기본 Member 객체 상수
+const _defaultMember = Member(id: '', email: '', nickname: '', uid: '', onAir: false, image: '');
```

</blockquote></details>
<details>
<summary>lib/community/data/data_source/mock_post_data_source_impl.dart (2)</summary><blockquote>

`23-23`: **주석 형식 개선 제안**

한글 주석이 있는데, 코드 내 주석의 일관성을 위해 영어로 작성하거나, 명확한 주석 형식으로 변경하는 것이 좋습니다. 또한 주석 내용이 코드 구현에 대한 배경을 설명하고 있어 유용합니다.

```diff
-        member: MemberDto(            // auth 완성 전까지 임시
+        member: MemberDto(            // Temporary until auth module is completed
```

---

`13-43`: **목 데이터 소스 구현이 잘 되었습니다**

`fetchPostList` 메서드의 구현이 잘 되었습니다. 500밀리초의 지연을 추가하여 실제 API 호출과 유사한 환경을 시뮬레이션하고 있으며, 다양한 상태의 목 데이터를 생성하고 있습니다. 이는 UI 개발 및 테스트에 매우 유용합니다.

한 가지 제안으로, 랜덤 숫자 생성을 위해 시드값이 있는 Random 인스턴스를 사용하면 테스트 시 일관된 결과를 얻을 수 있습니다:

```diff
-    final rand = Random();
+    final rand = Random(42); // 고정 시드값 사용
```

초보자를 위한 설명: 목(Mock) 데이터는 실제 API가 완성되기 전에 임시로 사용하는 가짜 데이터입니다. 시드값이 있는 Random은 항상 동일한 순서로 난수를 생성하므로, 테스트 시 결과가 일관되게 유지됩니다.

</blockquote></details>
<details>
<summary>lib/community/presentation/community_list/community_list_notifier.dart (1)</summary><blockquote>

`58-67`: **정렬 메서드의 코드 중복을 줄일 수 있습니다.**

`_applySort` 메서드에서 두 케이스 모두 비슷한 코드 패턴을 사용하고 있습니다. 정렬 로직만 다른 경우 코드 중복을 줄일 수 있습니다.

아래와 같이 리팩토링하면 코드가 더 간결해집니다:

```diff
  List<Post> _applySort(List<Post> list, CommunityTabType tab) {
+   final sorted = [...list];
    switch (tab) {
      case CommunityTabType.popular:
-       final sorted = [...list]..sort((a, b) => b.like.length.compareTo(a.like.length));
-       return sorted;
+       sorted.sort((a, b) => b.like.length.compareTo(a.like.length));
+       break;
      case CommunityTabType.newest:
-       final sorted = [...list]..sort((a, b) => b.createdAt.compareTo(a.createdAt));
-       return sorted;
+       sorted.sort((a, b) => b.createdAt.compareTo(a.createdAt));
+       break;
    }
+   return sorted;
  }
```

</blockquote></details>
<details>
<summary>lib/community/community.md (1)</summary><blockquote>

`1-44`: **요구사항 문서가 실제 코드에 잘 반영되었습니다.**

이 파일은 개발 요구사항을 담고 있으며, 코드베이스에 이러한 요구사항이 잘 반영된 것으로 보입니다. 그러나 이 파일은 실제 문서화 파일보다는 개발자 간의 대화 내용에 가깝습니다. 장기적인 유지보수를 위해 이 파일을 더 체계적인 문서로 변환하는 것이 좋을 것 같습니다.


아래와 같은 형식으로 문서를 재구성해보세요:

```markdown
# 커뮤니티 기능 요구사항 및 가이드라인

## 데이터 모델 정의
- Post 모델
  - id: String
  - title: String
  - content: String
  - ...

## 아키텍처 가이드라인
- DataSource 구현 방식
- Repository 패턴 적용 방법
- ...

## 상태 관리
- AsyncValue 처리를 위한 Notifier 사용
- ...
```

이렇게 문서화하면 나중에 다른 개발자가 이 기능을 이해하거나 수정할 때 도움이 될 것입니다.

</blockquote></details>
<details>
<summary>lib/community/presentation/community_list/community_list_screen.dart (2)</summary><blockquote>

`67-89`: **빈 목록 처리 로직에 중복이 있습니다.**

`_buildPostList` 메서드에서 빈 목록을 처리하는 로직이 중복되어 있습니다. 먼저 74행에서 `value.isNotEmpty ? value : []`로 빈 목록을 처리한 후, 75-77행에서 다시 `list.isEmpty`를 확인합니다.

아래와 같이 중복 로직을 제거할 수 있습니다:

```diff
  Widget? _buildPostList() {
    switch (state.postList) {
      case AsyncLoading():
        return const Center(child: CircularProgressIndicator());
      case AsyncError(:final error):
        return Center(child: Text((error as Failure).message));
      case AsyncData(:final value):
-       final list = value.isNotEmpty ? value : [];
-       if (list.isEmpty) {
+       if (value.isEmpty) {
          return const Center(child: Text('등록된 게시글이 없습니다'));
        }
        return ListView.builder(
          physics: const AlwaysScrollableScrollPhysics(),
-         itemCount: list.length,
+         itemCount: value.length,
          itemBuilder:
              (context, i) => PostListItem(
-               post: list[i],
-               onTap: () => onAction(CommunityListAction.tapPost(list[i].id)),
+               post: value[i],
+               onTap: () => onAction(CommunityListAction.tapPost(value[i].id)),
              ),
        );
    }
    return null;
  }
```

---

`44-45`: **빈 목록 처리 로직이 중복되어 있습니다.**

`_buildPostList` 메서드가 null을 반환하는 경우 44-45행에서 기본 텍스트를 표시하고 있습니다. 그런데 `_buildPostList` 메서드 내부에서도 빈 목록일 경우 동일한 텍스트를 표시하는 로직이 있습니다(76-77행). 이로 인해 같은 텍스트가 두 번 정의되어 있어 유지보수에 어려움이 있을 수 있습니다.

텍스트를 상수로 정의하거나 로직을 단일화하는 것이 좋습니다:

```diff
+ static const String emptyListMessage = '등록된 게시글이 없습니다';

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      // ... 기존 코드 ...
              Expanded(
                child:
                    _buildPostList() ??
-                   const Center(child: Text('등록된 게시글이 없습니다')),
+                   const Center(child: Text(emptyListMessage)),
              ),
      // ... 기존 코드 ...
    );
  }

  // ... 기존 코드 ...

  Widget? _buildPostList() {
    // ... 기존 코드 ...
      case AsyncData(:final value):
        if (value.isEmpty) {
-         return const Center(child: Text('등록된 게시글이 없습니다'));
+         return const Center(child: Text(emptyListMessage));
        }
    // ... 기존 코드 ...
  }
```

</blockquote></details>
<details>
<summary>lib/community/module/util/community_tab_type_enum.dart (1)</summary><blockquote>

`1-1`: **enum 선언으로 탭 타입을 명확히 분리한 점이 훌륭해요!**

커뮤니티 리스트 화면에서 사용할 탭을 `CommunityTabType` enum으로 정의하시어 코드의 가독성과 유지보수성을 크게 높이셨습니다.  
초보자분들도 enum의 역할과 쓰임을 쉽게 이해할 수 있도록, 아래와 같이 DartDoc 문서화 주석을 추가해 보시면 IDE 상에서 설명이 보여 더 직관적일 거예요.  

```diff
+/// 커뮤니티 리스트 화면에서 제공할 탭 종류를 나타내는 열거형입니다.
 enum CommunityTabType {
+  /// 인기순으로 정렬된 탭입니다.
   popular,
+  /// 최신순으로 정렬된 탭입니다.
   newest,
 }
```

추가로, UI 레이블이나 API 파라미터로 사용할 때 편리하도록 extension을 만들어 두시면 실무에서 더욱 도움이 될 것 같습니다.  
예시:
```dart
extension CommunityTabTypeExtension on CommunityTabType {
  String get label {
    switch (this) {
      case CommunityTabType.popular:
        return '인기글';
      case CommunityTabType.newest:
        return '최신글';
    }
  }
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 7e9d454bf8e983d26ce9ce1af05fe140cae0b907 and d77804d281704ad3e12da1f083719d304fe36c8d.

</details>

<details>
<summary>📒 Files selected for processing (28)</summary>

* `lib/community/community.md` (1 hunks)
* `lib/community/community_main.dart` (1 hunks)
* `lib/community/data/data_source/mock_post_data_source_impl.dart` (1 hunks)
* `lib/community/data/data_source/post_data_source.dart` (1 hunks)
* `lib/community/data/dto/comment_dto.dart` (1 hunks)
* `lib/community/data/dto/hash_tag_dto.dart` (1 hunks)
* `lib/community/data/dto/like_dto.dart` (1 hunks)
* `lib/community/data/dto/member_dto.dart` (1 hunks)
* `lib/community/data/dto/post_dto.dart` (1 hunks)
* `lib/community/data/mapper/post_mapper.dart` (1 hunks)
* `lib/community/data/repository_impl/post_repository_impl.dart` (1 hunks)
* `lib/community/domain/model/comment.dart` (1 hunks)
* `lib/community/domain/model/hash_tag.dart` (1 hunks)
* `lib/community/domain/model/like.dart` (1 hunks)
* `lib/community/domain/model/post.dart` (1 hunks)
* `lib/community/domain/repository/post_repository.dart` (1 hunks)
* `lib/community/domain/usecase/load_post_list_use_case.dart` (1 hunks)
* `lib/community/domain/usecase/switch_tab_use_case.dart` (1 hunks)
* `lib/community/module/community_di.dart` (1 hunks)
* `lib/community/module/community_router.dart` (1 hunks)
* `lib/community/module/util/community_tab_type_enum.dart` (1 hunks)
* `lib/community/module/util/board_type_enum.dart` (1 hunks)
* `lib/community/presentation/community_list/community_list_action.dart` (1 hunks)
* `lib/community/presentation/community_list/community_list_notifier.dart` (1 hunks)
* `lib/community/presentation/community_list/community_list_screen.dart` (1 hunks)
* `lib/community/presentation/community_list/community_list_screen_root.dart` (1 hunks)
* `lib/community/presentation/community_list/community_list_state.dart` (1 hunks)
* `lib/community/presentation/component/post_list_item.dart` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (26)</summary><blockquote>

<details>
<summary>lib/community/domain/repository/post_repository.dart (1)</summary>

`1-9`: **좋은 레포지토리 인터페이스 설계입니다.**

클린 아키텍처 원칙을 잘 따르고 있습니다! 도메인 계층에 추상 인터페이스를 정의하여 구현 세부사항으로부터 도메인 로직을 분리했네요.

`Result`를 반환 타입으로 사용한 것도 좋은 선택입니다. 이는 성공/실패 상태를 명확하게 처리할 수 있게 해주고, 예외 처리를 더 우아하게 할 수 있도록 도와줍니다.

초보자를 위한 설명:
- `abstract interface class`: 이 클래스는 구현해야 할 메서드의 '계약'만 정의하며, 실제 구현은 다른 클래스에서 이루어집니다.
- `Future<Result<List<Post>>>`: 비동기 작업의 결과로 `Post` 객체 목록을 `Result` 래퍼로 감싸 반환한다는 의미입니다.
  - `Future`: 비동기 작업을 나타냅니다.
  - `Result`: 성공 또는 실패 상태를 담는 래퍼 클래스입니다.
  - `List<Post>`: 성공 시 반환되는 `Post` 객체의 목록입니다.

향후 확장 시 다양한 필터나 정렬 옵션을 고려하여 메서드를 추가할 수 있습니다.

</details>
<details>
<summary>lib/community/domain/model/hash_tag.dart (1)</summary>

`1-11`: **Freezed를 활용한 불변 데이터 모델 구현이 잘 되었습니다.**

`freezed` 패키지를 사용하여 불변(immutable) 데이터 클래스를 정의한 것은 좋은 접근법입니다. 이렇게 하면 객체의 불변성을 보장하고, 코드를 더 안전하게 만들 수 있습니다.

초보자를 위한 설명:
- `@freezed`: 이 어노테이션은 코드 생성기에게 이 클래스를 위한, 불변성과 안전성이 보장된 코드를 생성하라고 지시합니다.
- `abstract class with _$HashTag`: 이는 Freezed 패턴으로, 실제 구현은 생성된 코드에 의해 제공됩니다.
- `const factory`: 팩토리 생성자를 통해 클래스의 인스턴스를 생성하는 방법을 정의합니다.

Freezed를 사용하면 다음과 같은 이점이 있습니다:
1. 자동으로 `toString()`, `==` 연산자, `hashCode` 등을 생성합니다.
2. 깊은 복사와 수정을 위한 `copyWith()` 메서드를 제공합니다.
3. 패턴 매칭과 같은 기능을 지원합니다.

이 파일은 Freezed 사용 패턴을 잘 따르고 있으며, 도메인 모델로서 적절하게 구현되었습니다.

</details>
<details>
<summary>lib/community/data/dto/member_dto.dart (2)</summary>

`2-4`: **`freezed` 및 JSON 직렬화 설정 확인**  
`package:freezed_annotation` 임포트와 `part` 디렉티브가 올바르게 구성되어 있어 코드 생성이 정상적으로 이루어질 것으로 보입니다.

---

`18-20`: **`fromJson` 팩토리 구현 확인**  
`MemberDto.fromJson`이 자동 생성된 `_$MemberDtoFromJson`을 사용하고 있어 JSON 역직렬화가 정상적으로 처리될 것입니다.

</details>
<details>
<summary>lib/community/data/dto/comment_dto.dart (3)</summary>

`2-4`: **`freezed` 설정 및 part 디렉티브 확인**  
`package:freezed_annotation` 임포트와 `part` 디렉티브 구성이 적절합니다. JSON 역직렬화 준비가 완료되어 있습니다.

---

`7-12`: **매핑 시 null 처리 유의**  
DTO에서는 nullable 필드를 사용하지만, 도메인 모델 `Comment`는 non-nullable로 설계되어 있습니다. `PostMapper` 등 매핑 로직에서 null 값을 안전하게 처리하거나 기본값을 지정하는지 확인해 주세요.

---

`14-16`: **`fromJson` 팩토리 구현 확인**  
`CommentDto.fromJson`이 자동 생성 메서드를 호출하여 JSON을 객체로 변환하도록 잘 구현되었습니다.

</details>
<details>
<summary>lib/community/data/dto/like_dto.dart (3)</summary>

`2-4`: **`freezed` 설정 및 part 디렉티브 확인**  
`package:freezed_annotation` 임포트와 `part` 디렉티브 구성이 올바르게 이루어졌습니다.

---

`8-11`: **매핑 시 null 처리 유의**  
`LikeDto`도 nullable 필드를 사용하므로, 도메인 모델 `Like`로 변환할 때 null 검사 또는 기본값 할당 로직이 있는지 검증해 주세요.

---

`13-15`: **`fromJson` 팩토리 구현 확인**  
`LikeDto.fromJson`이 생성된 `_$LikeDtoFromJson`을 호출하므로 JSON 역직렬화가 정상 동작합니다.

</details>
<details>
<summary>lib/community/domain/model/comment.dart (1)</summary>

`2-13`: **도메인 `Comment` 모델 확인**  
`freezed`를 사용해 모든 필드를 `required`로 정의한 점이 도메인 계층 설계 의도와 일치합니다. JSON 관련 코드를 포함하지 않아 DTO->도메인 매핑이 명확하게 분리된 구조입니다.

</details>
<details>
<summary>lib/community/data/dto/hash_tag_dto.dart (3)</summary>

`2-4`: **`freezed` 설정 및 part 디렉티브 확인**  
`package:freezed_annotation` 임포트와 `part` 디렉티브 구성이 적절하며, 코드 생성 준비가 완료되었습니다.

---

`8-11`: **매핑 시 null 처리 유의**  
`HashTagDto`의 nullable 필드를 도메인 모델 `HashTag`로 변환할 때 null 체크 또는 기본값 처리 로직이 구현되어 있는지 확인해 주세요.

---

`13-15`: **`fromJson` 팩토리 구현 확인**  
`HashTagDto.fromJson`이 자동 생성된 `_$HashTagDtoFromJson`을 사용해 JSON을 객체로 변환하도록 잘 구현되었습니다.

</details>
<details>
<summary>lib/community/data/mapper/post_mapper.dart (4)</summary>

`27-36`: **MemberDto 변환 로직 잘 구현되었습니다!**

`MemberDto`를 `Member` 도메인 모델로 변환하는 확장 메서드가 잘 구현되었습니다. 모든 필드에 대해 null 체크를 수행하여 안전한 변환을 보장하고 있습니다. 이러한 방식은 데이터의 일관성을 유지하는 데 매우 중요합니다.

---

`38-40`: **간결한 HashTagDto 변환 로직이 좋습니다**

`HashTagDto`를 `HashTag` 도메인 모델로 변환하는 확장 메서드가 간결하게 잘 구현되었습니다. 한 줄로 작성되어 있어 가독성이 좋으며, null 체크도 적절히 수행하고 있습니다.

---

`42-44`: **Like 변환 로직이 잘 구현되었습니다**

`LikeDto`를 `Like` 도메인 모델로 변환하는 확장 메서드가 간결하게 잘 구현되었습니다. null 체크를 통해 안전한 데이터 변환을 보장하고 있습니다.

---

`46-48`: **리스트 변환 확장 메서드 좋은 구현입니다**

`List<PostDto>`를 `List<Post>`로 변환하는 확장 메서드가 잘 구현되었습니다. 이 패턴은 컬렉션 데이터를 처리할 때 매우 유용하며, 코드의 재사용성과 가독성을 높여줍니다. `map`과 `toList`를 사용한 함수형 접근 방식이 효율적입니다.

</details>
<details>
<summary>lib/community/module/community_di.dart (4)</summary>

`13-15`: **PostDataSource 제공자 구현이 잘 되었습니다**

Riverpod을 사용한 의존성 주입(DI)이 잘 구현되었습니다. `@riverpod` 어노테이션을 사용하여 코드 생성을 활용하고 있으며, 이는 보일러플레이트 코드를 줄이는 현대적인 접근 방식입니다. 여기서는 `PostDataSourceImpl`의 인스턴스를 제공하고 있어요.

초보자를 위한 설명: 의존성 주입(DI)은 객체가 필요로 하는 다른 객체(의존성)를 외부에서 주입해주는 디자인 패턴입니다. Riverpod은 Flutter에서 상태 관리와 의존성 주입을 쉽게 할 수 있도록 도와주는 라이브러리예요.

---

`16-19`: **PostRepository 제공자 잘 구현되었습니다**

`postRepository` 제공자가 잘 구현되었습니다. `ref.watch(postDataSourceProvider)`를 통해 데이터 소스에 대한 의존성을 주입받고 있으며, 이를 사용하여 `PostRepositoryImpl`을 생성하고 있습니다. 이러한 계층화된 아키텍처는 코드의 유지보수성과 테스트 용이성을 높여줍니다.

초보자를 위한 설명: 여기서 `ref.watch`는 다른 제공자의 값을 구독하여 그 값이 변경될 때마다 이 제공자도 업데이트되도록 합니다. 이를 통해 의존성 간의 관계를 자동으로 관리할 수 있습니다.

---

`20-22`: **UseCase 제공자 잘 구현되었습니다**

`loadPostListUseCase` 제공자가 잘 구현되었습니다. 이 제공자는 `postRepository`를 의존성으로 주입받아 `LoadPostListUseCase`를 생성합니다. 이는 클린 아키텍처의 원칙을 잘 따르고 있습니다.

초보자를 위한 설명: UseCase는 비즈니스 로직을 캡슐화하는 클래스로, 특정 기능(예: 게시물 목록 불러오기)을 담당합니다. 이렇게 비즈니스 로직을 별도의 클래스로 분리하면 코드의 재사용성과 테스트 용이성이 향상됩니다.

---

`24-25`: **SwitchTabUseCase 제공자 잘 구현되었습니다**

`switchTabUseCase` 제공자가 간결하게 잘 구현되었습니다. 이 제공자는 `SwitchTabUseCase`의 새 인스턴스를 생성합니다. 다른 제공자들과 달리 의존성을 주입받지 않고 있는데, 이는 해당 UseCase가 독립적으로 동작할 수 있음을 의미합니다.

</details>
<details>
<summary>lib/community/presentation/community_list/community_list_action.dart (1)</summary>

`5-12`: **액션 클래스 설계가 잘 되었습니다**

`CommunityListAction` sealed 클래스와 다양한 액션 타입이 잘 정의되어 있습니다. Freezed 패키지를 사용하여 이벤트 처리를 위한 불변 객체를 생성하는 것은 좋은 접근 방식입니다. 각 액션이 명확하게 구분되어 있어 코드의 가독성과 유지보수성이 좋습니다.

초보자를 위한 설명: 
- `sealed class`는 모든 가능한 하위 타입이 동일한 파일에 정의되어야 하는 클래스로, 타입 안전성을 높여줍니다.
- Freezed는 불변 객체를 쉽게 만들기 위한 코드 생성 패키지입니다.
- 이렇게 액션을 명확히 구분하면 상태 관리가 더 쉬워지고, 컴파일 타임에 오류를 잡을 수 있습니다.

</details>
<details>
<summary>lib/community/presentation/community_list/community_list_notifier.dart (2)</summary>

`21-23`: **초기 상태 로딩 방식이 적절합니다.**

`Future.microtask`를 사용하여 빌드 메서드가 완료된 후 데이터를 가져오는 방식이 올바릅니다. 이렇게 하면 위젯 트리가 먼저 구성된 후 데이터 로딩이 시작되어 UI 렌더링 블로킹을 방지할 수 있습니다.

---

`27-37`: **비동기 데이터 로딩 처리가 잘 구현되어 있습니다.**

로딩 상태를 먼저 설정하고 데이터를 가져온 후 결과에 따라 상태를 업데이트하는 패턴이 잘 구현되어 있습니다. `AsyncValue`를 사용하여 로딩, 성공, 오류 상태를 명확하게 처리하고 있습니다.

</details>
<details>
<summary>lib/community/presentation/community_list/community_list_screen.dart (1)</summary>

`19-52`: **UI 구조가 잘 구성되어 있습니다.**

`DefaultTabController`를 사용하여 탭 기능을 구현하고, `AppBar`, `FloatingActionButton`, `RefreshIndicator` 등을 적절히 조합하여 사용자 경험을 향상시키는 UI를 구성했습니다. 위젯 구조가 명확하고 가독성이 좋습니다.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-02T06:27:48Z

Review comments:
diff_hunk: @@ -0,0 +1,5 @@
+import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+
+class SwitchTabUseCase {
+  CommunityTabType execute(CommunityTabType current, CommunityTabType next) => next;
+}
path: lib/community/domain/usecase/switch_tab_use_case.dart
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**import 경로에 문제가 있어요!**

현재 코드에 중요한 문제가 있습니다. import 문에 제어 문자(백스페이스, `%08`)가 포함되어 있어요. 이로 인해 코드가 예상대로 작동하지 않을 수 있습니다.

```diff
- import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+ import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
```

또한, 이 UseCase 클래스는 매우 단순한 기능을 수행합니다. 항상 next 값을 그대로 반환하고 있어요. 유스케이스는 일반적으로 비즈니스 로직을 캡슐화하는 역할을 하는데, 현재 코드는 별다른 로직이 없습니다. 만약 탭 전환에 추가적인 로직(예: 권한 검사, 데이터 검증 등)이 없다면, 이 유스케이스는 불필요할 수 있습니다.

하지만 미래에 탭 전환 과정에 복잡한 로직이 추가될 가능성을 고려한 설계라면, 확장성 측면에서는 좋은 접근입니다. 이런 경우 목적을 명확히 하는 주석을 추가하면 좋겠습니다.

```dart
/// 탭 전환을 처리하는 유스케이스
/// 현재는 단순 전환만 수행하지만, 추후 전환 전 데이터 검증이나 
/// 탭 전환 시 부가 작업을 추가할 수 있음
class SwitchTabUseCase {
  CommunityTabType execute(CommunityTabType current, CommunityTabType next) => next;
}
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T06:27:43Z
author_association: NONE
diff_hunk: @@ -0,0 +1,23 @@
+// lib/community/data/repository_impl/post_repository_impl.dart
+import 'package:devlink_mobile_app/community/data/data_source/post_data_source.dart';
+import 'package:devlink_mobile_app/community/data/mapper/post_mapper.dart';
+import 'package:devlink_mobile_app/community/domain/model/post.dart';
+import 'package:devlink_mobile_app/community/domain/repository/post_repository.dart';
+import 'package:devlink_mobile_app/core/result/result.dart';
+
+
+class PostRepositoryImpl implements PostRepository {
+  const PostRepositoryImpl({required PostDataSource dataSource}) : _remote = dataSource;
+
+  final PostDataSource _remote;
+
+  @override
+  Future<Result<List<Post>>> loadPostList() async {
+    try {
+      final dto = await _remote.fetchPostList();
+      return Result.success(dto.toModelList());
+    } catch (e) {
+      return Result.error(mapExceptionToFailure(e, StackTrace.fromString(e.toString())));
+    }
path: lib/community/data/repository_impl/post_repository_impl.dart
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**오류 처리 부분을 개선해 주세요**

현재 코드에서 `mapExceptionToFailure` 함수가 사용되고 있지만, 이 함수가 어디에 정의되어 있는지 이 파일에서는 명확하지 않습니다. 또한, `StackTrace.fromString(e.toString())`은 실제 스택 트레이스 정보를 제대로 캡처하지 못할 수 있습니다.

다음과 같이 개선할 수 있습니다:

```diff
- catch (e) {
-   return Result.error(mapExceptionToFailure(e, StackTrace.fromString(e.toString())));
- }
+ catch (e, stackTrace) {
+   // core/failure 패키지에서 적절한 Failure 타입으로 변환하는 유틸리티 함수를 import 합니다
+   return Result.error(mapExceptionToFailure(e, stackTrace));
+ }
```

`mapExceptionToFailure` 함수를 사용하려면 해당 함수가 정의된 유틸리티 클래스나 파일을 import 해야 합니다. 아직 이 함수가 구현되지 않았다면, 별도의 유틸리티 클래스에서 구현해 주세요.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    } catch (e, stackTrace) {
      // core/failure 패키지에서 적절한 Failure 타입으로 변환하는 유틸리티 함수를 import 합니다
      return Result.error(mapExceptionToFailure(e, stackTrace));
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T06:27:43Z
author_association: NONE
diff_hunk: @@ -0,0 +1,17 @@
+import 'package:devlink_mobile_app/community/domain/model/post.dart';
+import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
path: lib/community/presentation/community_list/community_list_state.dart
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**import 경로에 특수문자가 포함되어 있습니다**

현재 import 경로에 `%08` 문자가 포함되어 있습니다. 이것은 ASCII 코드로 백스페이스(backspace) 문자를 나타내며, 빌드 오류나 런타임 오류를 발생시킬 수 있습니다.

다음과 같이 수정해주세요:

```diff
- import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+ import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
```

특수문자가 없는 정확한 경로를 사용하면 안정적인 빌드와 실행이 가능합니다.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T06:27:43Z
author_association: NONE
diff_hunk: @@ -0,0 +1,12 @@
+import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
path: lib/community/presentation/community_list/community_list_action.dart
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Import 경로에 비정상 문자가 포함되어 있습니다**

Import 경로에 백스페이스 문자(\b)가 포함되어 있어 컴파일 오류를 발생시킬 수 있습니다. 이 문자는 눈에 보이지 않지만 'community_tab_type_enum.dart' 앞에 위치해 있습니다.

```diff
- import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+ import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
```




<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T06:27:43Z
author_association: NONE
diff_hunk: @@ -0,0 +1,68 @@
+// lib/community/presentation/community_list_notifier.dart
+import 'dart:async';
+import 'package:devlink_mobile_app/community/domain/model/post.dart';
+import 'package:devlink_mobile_app/community/domain/usecase/load_post_list_use_case.dart';
+import 'package:devlink_mobile_app/community/module/community_di.dart';
+import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
path: lib/community/presentation/community_list/community_list_notifier.dart
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**import 경로에 특수 문자가 포함되어 있습니다.**

import 경로에 백스페이스(\b) 특수 문자가 포함되어 있습니다. 이는 컴파일 오류를 발생시킬 수 있습니다.


다음과 같이 수정해주세요:

```diff
-import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
```

`%08`은 백스페이스 문자의 URL 인코딩 형태로, 이를 제거해야 합니다.


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T06:27:43Z
author_association: NONE
diff_hunk: @@ -0,0 +1,90 @@
+import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
path: lib/community/presentation/community_list/community_list_screen.dart
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**import 경로에 특수 문자가 포함되어 있습니다.**

import 경로에 백스페이스(\b) 특수 문자가 포함되어 있습니다. 이는 컴파일 오류를 발생시킬 수 있습니다.


다음과 같이 수정해주세요:

```diff
-import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
```

`%08`은 백스페이스 문자의 URL 인코딩 형태로, 이를 제거해야 합니다.


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T06:27:43Z
author_association: NONE
diff_hunk: @@ -0,0 +1,5 @@
+import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
path: lib/community/domain/usecase/switch_tab_use_case.dart
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**파일 경로에 특수 문자가 포함되어 있습니다.**

import 문에 `%08` 특수 문자(ASCII 백스페이스 문자)가 포함되어 있네요. 이로 인해 컴파일 시 오류가 발생할 수 있습니다. 아래와 같이 수정해 주세요:

```diff
- import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+ import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
```

특수 문자는 파일 경로에서 인식되지 않거나 잘못된 경로로 인식될 수 있어 임포트가 실패할 수 있습니다.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T06:27:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,29 @@
+import 'package:devlink_mobile_app/community/domain/model/post.dart';
+import 'package:flutter/material.dart';
+
+
+class PostListItem extends StatelessWidget {
+  const PostListItem({
+    super.key,
+    required this.post,
+    required this.onTap,
+  });
+
+  final Post post;
+  final VoidCallback onTap;
+
+  @override
+  Widget build(BuildContext context) => ListTile(
+        title: Text(post.title),
+        subtitle: Text('${post.member.nickname} · ${post.createdAt.toLocal()}'),
path: lib/community/presentation/component/post_list_item.dart
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**날짜 형식을 사용자 친화적으로 개선해 보세요.**

현재 날짜가 `toLocal()`을 사용해 표시되고 있지만, 이는 사용자에게 친숙한 형식이 아닐 수 있습니다. 날짜와 시간을 더 읽기 쉬운 형식으로 표시하면 좋겠습니다.

`intl` 패키지의 `DateFormat` 클래스를 사용하여 날짜를 포맷팅하는 것을 권장합니다:

```diff
- subtitle: Text('${post.member.nickname} · ${post.createdAt.toLocal()}'),
+ subtitle: Text('${post.member.nickname} · ${DateFormat('yyyy.MM.dd HH:mm').format(post.createdAt.toLocal())}'),
```

이를 위해서는 패키지를 추가하고 import 문도 필요합니다:

```dart
import 'package:intl/intl.dart';
```

초보자를 위한 설명:
- `DateFormat`: 날짜와 시간을 지정된 패턴에 따라 문자열로 변환해주는 클래스입니다.
- `'yyyy.MM.dd HH:mm'`: 연도, 월, 일, 시간, 분 형식을 지정합니다.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T06:27:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,17 @@
+import 'package:devlink_mobile_app/community/domain/model/post.dart';
+import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+
path: lib/community/presentation/community_list/community_list_state.dart
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**import 경로에 숨겨진 문자가 있습니다!**

2번 줄의 import 경로에 눈에 보이지 않는 특수 문자(제어 문자 \b, 백스페이스)가 포함되어 있습니다. 이로 인해 빌드 시 오류가 발생할 수 있습니다.

```diff
- import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+ import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
```

이런 숨겨진 문자는 파일을 복사하거나 편집기를 전환할 때 생길 수 있습니다. 수정 후 정상적으로 동작하는지 확인해 주세요!



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import 'package:devlink_mobile_app/community/domain/model/post.dart';
import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T06:27:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,23 @@
+// lib/community/data/repository_impl/post_repository_impl.dart
+import 'package:devlink_mobile_app/community/data/data_source/post_data_source.dart';
+import 'package:devlink_mobile_app/community/data/mapper/post_mapper.dart';
+import 'package:devlink_mobile_app/community/domain/model/post.dart';
+import 'package:devlink_mobile_app/community/domain/repository/post_repository.dart';
+import 'package:devlink_mobile_app/core/result/result.dart';
+
+
+class PostRepositoryImpl implements PostRepository {
+  const PostRepositoryImpl({required PostDataSource dataSource}) : _remote = dataSource;
+
+  final PostDataSource _remote;
+
+  @override
+  Future<Result<List<Post>>> loadPostList() async {
+    try {
+      final dto = await _remote.fetchPostList();
+      return Result.success(dto.toModelList());
+    } catch (e) {
+      return Result.error(mapExceptionToFailure(e, StackTrace.fromString(e.toString())));
+    }
+  }
+}
path: lib/community/data/repository_impl/post_repository_impl.dart
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**리포지토리 구현이 깔끔하고 견고합니다!**

`PostRepositoryImpl` 클래스가 관심사 분리와 의존성 주입 원칙을 잘 따르고 있습니다. 데이터 소스로부터 DTO를 가져와 도메인 모델로 변환하는 과정이 명확하게 구현되어 있습니다.

한 가지 개선할 수 있는 부분:
1. 에러 처리 부분에서 `StackTrace.fromString(e.toString())`보다는 실제 스택 트레이스를 사용하는 것이 더 정확한 디버깅 정보를 제공할 수 있습니다. 

```diff
@override
Future<Result<List<Post>>> loadPostList() async {
  try {
    final dto = await _remote.fetchPostList();
    return Result.success(dto.toModelList());
-  } catch (e) {
-    return Result.error(mapExceptionToFailure(e, StackTrace.fromString(e.toString())));
+  } catch (e, stackTrace) {
+    return Result.error(mapExceptionToFailure(e, stackTrace));
  }
}
```




<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
// lib/community/data/repository_impl/post_repository_impl.dart
import 'package:devlink_mobile_app/community/data/data_source/post_data_source.dart';
import 'package:devlink_mobile_app/community/data/mapper/post_mapper.dart';
import 'package:devlink_mobile_app/community/domain/model/post.dart';
import 'package:devlink_mobile_app/community/domain/repository/post_repository.dart';
import 'package:devlink_mobile_app/core/result/result.dart';

class PostRepositoryImpl implements PostRepository {
  const PostRepositoryImpl({required PostDataSource dataSource}) : _remote = dataSource;

  final PostDataSource _remote;

  @override
  Future<Result<List<Post>>> loadPostList() async {
    try {
      final dto = await _remote.fetchPostList();
      return Result.success(dto.toModelList());
    } catch (e, stackTrace) {
      return Result.error(mapExceptionToFailure(e, stackTrace));
    }
  }
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T06:27:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,12 @@
+import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
path: lib/community/presentation/community_list/community_list_action.dart
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**import 경로에 특수문자 오류가 있습니다**

import 문에 백스페이스 특수문자(`\b`)가 포함되어 있습니다. 이는 컴파일 오류를 발생시킬 수 있으니 수정이 필요합니다:

```diff
-import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
```

초보자를 위한 설명: %08은 백스페이스를 나타내는 ASCII 코드입니다. 이런 특수문자가 경로에 포함되면 파일을 찾을 수 없는 오류가 발생할 수 있습니다.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T06:27:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,68 @@
+// lib/community/presentation/community_list_notifier.dart
+import 'dart:async';
+import 'package:devlink_mobile_app/community/domain/model/post.dart';
+import 'package:devlink_mobile_app/community/domain/usecase/load_post_list_use_case.dart';
+import 'package:devlink_mobile_app/community/module/community_di.dart';
+import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
path: lib/community/presentation/community_list/community_list_notifier.dart
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**import 경로에 문제가 있습니다.**

import 문에 백스페이스 문자(`%08`)가 포함되어 있습니다. 이 문자는 파일을 찾는 데 문제를 일으킬 수 있습니다.

아래와 같이 수정해주세요:

```diff
-import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T06:27:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,90 @@
+import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
path: lib/community/presentation/community_list/community_list_screen.dart
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**import 경로에 문제가 있습니다.**

import 문에 백스페이스 문자(`%08`)가 포함되어 있습니다. 이 문자는 파일을 찾는 데 문제를 일으킬 수 있습니다.

아래와 같이 수정해주세요:

```diff
-import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import 'package:devlink_mobile_app/community/module/util/community_tab_type_enum.dart';
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T06:27:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,90 @@
+import 'package:devlink_mobile_app/community/module/util/%08community_tab_type_enum.dart';
+import 'package:devlink_mobile_app/core/result/result.dart';
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'community_list_state.dart';
+import 'community_list_action.dart';
+import '../component/post_list_item.dart';
+
+class CommunityListScreen extends StatelessWidget {
+  const CommunityListScreen({
+    super.key,
+    required this.state,
+    required this.onAction,
+  });
+
+  final CommunityListState state;
+  final void Function(CommunityListAction action) onAction;
+
+  @override
+  Widget build(BuildContext context) {
+    return DefaultTabController(
+      length: 2,
+      child: Scaffold(
+        appBar: AppBar(
+          title: const Text('커뮤니티'),
+          actions: [
+            IconButton(
+              icon: const Icon(Icons.search),
+              onPressed: () => onAction(const CommunityListAction.tapSearch()),
+            ),
+          ],
+        ),
+        floatingActionButton: FloatingActionButton(
+          onPressed: () => onAction(const CommunityListAction.tapWrite()),
+          child: const Icon(Icons.edit),
+        ),
+        body: RefreshIndicator(
+          onRefresh: () async => onAction(const CommunityListAction.refresh()),
+          child: Column(
+            children: [
+              _buildTabBar(),
+              Expanded(
+                child:
+                    _buildPostList() ??
+                    const Center(child: Text('등록된 게시글이 없습니다')),
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
+  Widget _buildTabBar() => TabBar(
+    labelColor: Colors.blue,
+    indicatorColor: Colors.blue,
+    unselectedLabelColor: Colors.grey,
+    onTap:
+        (i) => onAction(
+          CommunityListAction.changeTab(
+            i == 0 ? CommunityTabType.popular : CommunityTabType.newest,
+          ),
+        ),
+    tabs: const [Tab(text: '인기순'), Tab(text: '최신순')],
+  );
+
+  Widget? _buildPostList() {
+    switch (state.postList) {
+      case AsyncLoading():
+        return const Center(child: CircularProgressIndicator());
+      case AsyncError(:final error):
+        return Center(child: Text((error as Failure).message));
path: lib/community/presentation/community_list/community_list_screen.dart
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**오류 타입 캐스팅에 주의가 필요합니다.**

오류를 처리할 때 `(error as Failure).message`와 같이 타입 캐스팅을 하고 있습니다. 만약 오류가 `Failure` 타입이 아닌 다른 타입(예: `Exception`)이라면 런타임 오류가 발생할 수 있습니다.

아래와 같이 안전하게 처리하는 방법을 고려해보세요:

```diff
- return Center(child: Text((error as Failure).message));
+ return Center(child: Text(
+   error is Failure 
+     ? (error as Failure).message 
+     : '오류가 발생했습니다: ${error.toString()}'
+ ));
```

이렇게 하면 다양한 오류 유형을 안전하게 처리할 수 있습니다.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T06:27:48Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-02T06:24:28Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Summary by CodeRabbit

- **신규 기능**
  - 커뮤니티 게시글 목록, 해시태그, 좋아요, 댓글 등 도메인 및 DTO 모델 추가
  - 게시글 목록 조회를 위한 목업 데이터 소스 및 저장소 구현
  - 게시글 목록 화면, 탭(인기순/최신순) 전환, 검색 및 작성 버튼 UI 제공
  - 게시글 목록 상태 관리 및 액션 처리(새로고침, 탭 변경, 게시글 선택 등) 도입
  - 라우팅 및 의존성 주입 구성

- **문서화**
  - 커뮤니티 기능 구조와 요구사항을 설명하는 문서 추가
## Walkthrough

이번 변경 사항은 커뮤니티 기능을 위한 데이터 모델, 데이터 소스, 레포지토리, 유즈케이스, 프레젠테이션(UI) 계층까지 전체적인 구조를 설계하고 구현하는 내용입니다. 데이터 전달 객체(DTO)와 도메인 모델, enum, DI(의존성 주입), 라우팅, 상태 관리, 화면 UI 등이 새롭게 추가되었습니다. 또한, 요구사항과 개발 가이드를 담은 문서도 포함되어 있습니다. 각 계층은 Riverpod과 Freezed 등 최신 플러터 생태계 도구를 활용해 설계되었으며, 앞으로의 확장성과 유지보수성을 고려한 폴더 구조와 코드 분리가 이루어졌습니다.

## Changes

| 파일/그룹 | 변경 요약 |
|-----------|-----------|
| `lib/community/community.md` | 커뮤니티 기능 리팩토링 및 구현을 위한 상세 요구사항 및 가이드 문서 추가 |
| `lib/community/community_main.dart` | 커뮤니티 전용 앱 진입점 및 라우터 연동 메인 위젯 추가 |
| `lib/community/data/data_source/post_data_source.dart`<br>`mock_post_data_source_impl.dart` | 게시글 데이터 소스 인터페이스 및 목(mock) 구현체 추가 |
| `lib/community/data/dto/*.dart` | Post, Member, Like, Comment, HashTag용 DTO 클래스(Freezed 기반, 필드 Nullable) 추가 |
| `lib/community/data/mapper/post_mapper.dart` | DTO → 도메인 모델 변환용 매퍼 확장 메서드 추가 |
| `lib/community/data/repository_impl/post_repository_impl.dart` | PostRepository 인터페이스 구현체 및 데이터 소스 연동, 예외 처리 추가 |
| `lib/community/domain/model/*.dart` | 게시글, 멤버, 좋아요, 해시태그, 댓글 도메인 모델(Freezed 기반) 추가 |
| `lib/community/domain/repository/post_repository.dart` | 게시글 리스트 로드용 레포지토리 인터페이스 추가 |
| `lib/community/domain/usecase/load_post_list_use_case.dart`<br>`switch_tab_use_case.dart` | 게시글 리스트 로드, 탭 전환 유즈케이스 클래스 추가 |
| `lib/community/module/community_di.dart` | Riverpod 기반 DI(의존성 주입) 모듈 및 Provider 정의 |
| `lib/community/module/community_router.dart` | GoRouter 기반 커뮤니티 라우팅 설정 및 Provider 추가 |
| `lib/community/module/util/board_type_enum.dart`<br>`community_tab_type_enum.dart` | 게시판 타입, 커뮤니티 탭 타입 Enum 정의 |
| `lib/community/presentation/community_list/community_list_action.dart` | 커뮤니티 리스트 화면에서 발생하는 액션(이벤트) Freezed 유니언 타입 정의 |
| `lib/community/presentation/community_list/community_list_state.dart` | 커뮤니티 리스트 상태(AsyncValue, 현재 탭) Freezed 클래스 정의 |
| `lib/community/presentation/community_list/community_list_notifier.dart` | 커뮤니티 리스트 상태 관리용 Notifier 클래스 및 액션 핸들링 로직 추가 |
| `lib/community/presentation/community_list/community_list_screen.dart` | 탭, 게시글 목록, 새로고침, 검색, 글쓰기 등 UI 및 액션 콜백 연동 화면 추가 |
| `lib/community/presentation/community_list/community_list_screen_root.dart` | Notifier와 UI, 라우팅을 연결하는 최상위 위젯 추가 |
| `lib/community/presentation/component/post_list_item.dart` | 게시글 아이템 UI 컴포넌트 추가 |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant CommunityListScreen
    participant CommunityListNotifier
    participant LoadPostListUseCase
    participant PostRepository
    participant PostDataSource

    User->>CommunityListScreen: 새로고침/탭 변경/검색/글쓰기/게시글 선택
    CommunityListScreen->>CommunityListNotifier: onAction(action)
    alt Refresh or ChangeTab
        CommunityListNotifier->>LoadPostListUseCase: execute()
        LoadPostListUseCase->>PostRepository: loadPostList()
        PostRepository->>PostDataSource: fetchPostList()
        PostDataSource-->>PostRepository: List<PostDto>
        PostRepository-->>LoadPostListUseCase: Result<List<Post>>
        LoadPostListUseCase-->>CommunityListNotifier: Result<List<Post>>
        CommunityListNotifier-->>CommunityListScreen: 상태 갱신
    else TapPost/TapSearch/TapWrite
        CommunityListNotifier-->>CommunityListScreen: (무시, UI에서 라우팅 처리)
    end
```

## Suggested reviewers

- doggodking
- MentalJava
- complete0415Jiyoung
- seonho99
- hemu0-0
- successhelper2000

---

### 용어 설명 및 맥락 안내

- **Freezed**: 불변(immutable) 데이터 모델, 패턴 매칭, 복사(copyWith) 등을 쉽게 만들어주는 코드 생성 도구입니다.
- **Riverpod**: 플러터에서 상태 관리와 DI(의존성 주입)를 쉽게 해주는 라이브러리입니다.
- **DTO(Data Transfer Object)**: 데이터 전송을 위한 객체로, 주로 네트워크 통신이나 데이터 계층에서 사용됩니다.
- **도메인 모델**: 실제 비즈니스 로직에서 사용하는 핵심 데이터 구조입니다.
- **AsyncValue**: 비동기 데이터의 상태(로딩, 성공, 실패)를 표현하는 Riverpod의 타입입니다.
- **Notifier**: 상태 변화를 관리하고, 외부 액션(이벤트)에 반응하는 객체입니다.
- **DI(Dependency Injection)**: 객체 생성과 의존성 관리를 분리하여 코드의 유연성과 테스트 용이성을 높이는 패턴입니다.
- **GoRouter**: 플러터에서 화면 전환(라우팅)을 쉽게 해주는 라이브러리입니다.

---

혹시 이해가 어려운 부분이나, 추가로 궁금한 용어가 있다면 언제든 질문해 주세요! 😊

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=devlink-community/gaesubang-app&utm_content=60):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: doggodking
created_at: 2025-05-02T06:24:50Z
body: 오우 좋습니다
author_association: CONTRIBUTOR