From 22569d2312c7d7af7dd1a263d000808536505a0b Mon Sep 17 00:00:00 2001 From: GI JEONG HONG Date: Wed, 19 Aug 2026 17:27:25 +0900 Subject: [PATCH 1/5] =?UTF-8?q?refactor:=20Chat=EC=9D=84=20LostItemChat?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Koin/Apps/SceneDelegate.swift | 8 +- Koin/Core/Extensions/Common/String+.swift | 6 +- .../LostItemChat/LostItemChatDetailDto.swift} | 13 +- .../LostItemChat/LostItemChatRoomDto.swift} | 12 +- .../LostItemCreateChatRoomResponse.swift} | 4 +- .../LostItemPostChatDetailRequest.swift} | 4 +- .../Repository/DefaultChatRepository.swift | 37 --- .../DefaultLostItemRepository.swift | 20 ++ Koin/Data/Service/ChatService.swift | 42 --- Koin/Data/Service/LostItemService.swift | 26 ++ Koin/Data/Service/Network/API/ChatAPI.swift | 70 ----- .../Service/Network/API/LostItemAPI.swift | 31 ++- .../LostItemChatDateInfo.swift} | 4 +- .../LostItemChatHistoryData.swift} | 6 +- .../LostItemChatRoomItem.swift} | 6 +- Koin/Domain/Repository/ChatRepository.swift | 16 -- .../Repository/LostItemRepository.swift | 6 + .../UseCase/Chat/CreateChatRoomUseCase.swift | 25 -- .../UseCase/Chat/FetchChatRoomUseCase.swift | 26 -- .../UseCase/Chat/PostChatDetailUseCase.swift | 27 -- .../LostItemBlockUserUseCase.swift} | 10 +- .../LostItemCreateChatRoomUseCase.swift | 24 ++ .../LostItemFetchChatDetailUseCase.swift} | 14 +- .../LostItemFetchChatRoomUseCase.swift | 26 ++ .../LostItemPostChatDetailUseCase.swift | 27 ++ .../Category/CategoryHostingController.swift | 2 +- .../Home/Home/HomeHostingController.swift | 2 +- .../NotificationViewController.swift | 8 +- ...stItemBlockCheckModalViewController.swift} | 6 +- .../LostItemChatDateHeaderView.swift} | 10 +- .../LostItemChatHistoryTableView.swift} | 30 +-- .../LostItemChatImageTableViewCell.swift} | 10 +- .../LostItemChatTextTableViewCell.swift} | 10 +- .../LostItemChatViewController.swift} | 18 +- .../LostItemChat/LostItemChatViewModel.swift} | 22 +- ...LostItemChatListTableViewController.swift} | 22 +- .../LostItemChatListTableViewModel.swift} | 19 +- .../LostItemDataViewController.swift | 10 +- .../LostItemData/LostItemDataViewModel.swift | 6 +- .../LostItemListViewController.swift | 4 +- .../PostLostItemViewController.swift | 4 +- koin.xcodeproj/project.pbxproj | 240 ++++++++---------- 42 files changed, 393 insertions(+), 520 deletions(-) rename Koin/Data/DTOs/Decodable/{Chat/ChatDetailDto.swift => LostItem/LostItemChat/LostItemChatDetailDto.swift} (71%) rename Koin/Data/DTOs/Decodable/{Chat/ChatRoomDto.swift => LostItem/LostItemChat/LostItemChatRoomDto.swift} (81%) rename Koin/Data/DTOs/Decodable/{Chat/CreateChatRoomResponse.swift => LostItem/LostItemChat/LostItemCreateChatRoomResponse.swift} (84%) rename Koin/Data/DTOs/Encodable/{Chat/PostChatDetailRequest.swift => LostItem/LostItemChat/LostItemPostChatDetailRequest.swift} (77%) delete mode 100644 Koin/Data/Repository/DefaultChatRepository.swift delete mode 100644 Koin/Data/Service/ChatService.swift delete mode 100644 Koin/Data/Service/Network/API/ChatAPI.swift rename Koin/Domain/Model/{Chat/ChatDateInfo.swift => LostItem/LostItemChatDateInfo.swift} (81%) rename Koin/Domain/Model/{Chat/ChatHistoryData.swift => LostItem/LostItemChatHistoryData.swift} (65%) rename Koin/Domain/Model/{Chat/ChatRoomItem.swift => LostItem/LostItemChatRoomItem.swift} (73%) delete mode 100644 Koin/Domain/Repository/ChatRepository.swift delete mode 100644 Koin/Domain/UseCase/Chat/CreateChatRoomUseCase.swift delete mode 100644 Koin/Domain/UseCase/Chat/FetchChatRoomUseCase.swift delete mode 100644 Koin/Domain/UseCase/Chat/PostChatDetailUseCase.swift rename Koin/Domain/UseCase/{Chat/BlockUserUseCase.swift => LostItem/LostItemBlockUserUseCase.swift} (62%) create mode 100644 Koin/Domain/UseCase/LostItem/LostItemCreateChatRoomUseCase.swift rename Koin/Domain/UseCase/{Chat/FetchChatDetailUseCase.swift => LostItem/LostItemFetchChatDetailUseCase.swift} (57%) create mode 100644 Koin/Domain/UseCase/LostItem/LostItemFetchChatRoomUseCase.swift create mode 100644 Koin/Domain/UseCase/LostItem/LostItemPostChatDetailUseCase.swift rename Koin/Presentation/{Chat/Chat/BlockCheckModalViewController.swift => LostItem/LostItemChat/LostItemBlockCheckModalViewController.swift} (93%) rename Koin/Presentation/{Chat/Chat/ChatHistoryTableView/ChatDateHeaderView.swift => LostItem/LostItemChat/LostItemChatHistoryTableView/LostItemChatDateHeaderView.swift} (84%) rename Koin/Presentation/{Chat/Chat/ChatHistoryTableView/ChatHistoryTableView.swift => LostItem/LostItemChat/LostItemChatHistoryTableView/LostItemChatHistoryTableView.swift} (77%) rename Koin/Presentation/{Chat/Chat/ChatHistoryTableView/ChatImageTableViewCell.swift => LostItem/LostItemChat/LostItemChatHistoryTableView/LostItemChatImageTableViewCell.swift} (91%) rename Koin/Presentation/{Chat/Chat/ChatHistoryTableView/ChatTextTableViewCell.swift => LostItem/LostItemChat/LostItemChatHistoryTableView/LostItemChatTextTableViewCell.swift} (90%) rename Koin/Presentation/{Chat/Chat/ChatViewController.swift => LostItem/LostItemChat/LostItemChatViewController.swift} (94%) rename Koin/Presentation/{Chat/Chat/ChatViewModel.swift => LostItem/LostItemChat/LostItemChatViewModel.swift} (85%) rename Koin/Presentation/{Chat/ChatList/ChatListTableViewController.swift => LostItem/LostItemChatList/LostItemChatListTableViewController.swift} (89%) rename Koin/Presentation/{Chat/ChatList/ChatListTableViewModel.swift => LostItem/LostItemChatList/LostItemChatListTableViewModel.swift} (79%) diff --git a/Koin/Apps/SceneDelegate.swift b/Koin/Apps/SceneDelegate.swift index a92eeeeb2..6d1c01f91 100644 --- a/Koin/Apps/SceneDelegate.swift +++ b/Koin/Apps/SceneDelegate.swift @@ -137,8 +137,8 @@ extension SceneDelegate { case .chat: if let articleId = Int(parsedQuery["articleId"]), let chatRoomId = Int(parsedQuery["chatRoomId"]) { - let viewModel = ChatViewModel(articleId: articleId, chatRoomId: chatRoomId, articleTitle: nil) - let chatViewController = ChatViewController(viewModel: viewModel) + let viewModel = LostItemChatViewModel(articleId: articleId, chatRoomId: chatRoomId, articleTitle: nil) + let chatViewController = LostItemChatViewController(viewModel: viewModel) navigationController?.pushViewController(chatViewController, animated: true) } case .callvan: @@ -373,13 +373,13 @@ extension SceneDelegate { private func makeLostItemData(lostItemId: Int) -> UIViewController { let userRepository = DefaultUserRepository(service: DefaultUserService()) let lostItemRepository = DefaultLostItemRepository(service: DefaultLostItemService()) - let chatRepository = DefaultChatRepository(service: DefaultChatService()) + let chatRepository = DefaultLostItemRepository(service: DefaultLostItemService()) let checkLoginUseCase = DefaultCheckLoginUseCase(userRepository: userRepository) let fetchLostItemDataUseCase = DefaultFetchLostItemDataUseCase(repository: lostItemRepository) let fetchLostItemListUseCase = DefaultFetchLostItemListUseCase(repository: lostItemRepository) let changeLostItemStateUseCase = DefaultChangeLostItemStateUseCase(repository: lostItemRepository) let deleteLostItemUseCase = DefaultDeleteLostItemUseCase(repository: lostItemRepository) - let createChatRoomUseCase = DefaultCreateChatRoomUseCase(chatRepository: chatRepository) + let createChatRoomUseCase = DefaultLostItemCreateChatRoomUseCase(chatRepository: chatRepository) let logAnalyticsEventUseCase = DefaultLogAnalyticsEventUseCase(repository: GA4AnalyticsRepository(service: GA4AnalyticsService())) let viewModel = LostItemDataViewModel( checkLoginUseCase: checkLoginUseCase, diff --git a/Koin/Core/Extensions/Common/String+.swift b/Koin/Core/Extensions/Common/String+.swift index 3418e1cec..e226f405b 100644 --- a/Koin/Core/Extensions/Common/String+.swift +++ b/Koin/Core/Extensions/Common/String+.swift @@ -10,7 +10,7 @@ import Kingfisher import SwiftSoup extension String { - func toChatDateInfo() -> ChatDateInfo { + func toLostItemChatDateInfo() -> LostItemChatDateInfo { let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] formatter.timeZone = TimeZone(secondsFromGMT: 0) // ✅ UTC 그대로 변환 @@ -31,7 +31,7 @@ extension String { // ✅ 문자열을 Date 타입으로 변환 (UTC 기준) guard let date = formatter.date(from: formattedDateString) else { print("❌ 변환 실패: \(formattedDateString)") - return ChatDateInfo( + return LostItemChatDateInfo( year: 0, month: 0, day: 0, hour: 0, minute: 0, second: 0, isToday: false, isYesterday: false, showingText: "날짜 오류" ) @@ -81,7 +81,7 @@ extension String { } - return ChatDateInfo( + return LostItemChatDateInfo( year: components.year ?? 0, month: components.month ?? 0, day: components.day ?? 0, diff --git a/Koin/Data/DTOs/Decodable/Chat/ChatDetailDto.swift b/Koin/Data/DTOs/Decodable/LostItem/LostItemChat/LostItemChatDetailDto.swift similarity index 71% rename from Koin/Data/DTOs/Decodable/Chat/ChatDetailDto.swift rename to Koin/Data/DTOs/Decodable/LostItem/LostItemChat/LostItemChatDetailDto.swift index e53f89411..05f7fbc2d 100644 --- a/Koin/Data/DTOs/Decodable/Chat/ChatDetailDto.swift +++ b/Koin/Data/DTOs/Decodable/LostItem/LostItemChat/LostItemChatDetailDto.swift @@ -1,5 +1,5 @@ // -// ChatDetailDto.swift +// LostItemChatDetailDto.swift // koin // // Created by 김나훈 on 2/18/25. @@ -7,7 +7,7 @@ import Foundation -struct ChatDetailDto: Codable { +struct LostItemChatDetailDto: Codable { let userId: Int let userNickname, content, timestamp: String let isImage: Bool @@ -20,15 +20,14 @@ struct ChatDetailDto: Codable { } } -extension ChatDetailDto { - func toDomain(currentUserId: Int) -> ChatMessage { - return ChatMessage( +extension LostItemChatDetailDto { + func toDomain(currentUserId: Int) -> LostItemChatMessage { + return LostItemChatMessage( senderNickname: userNickname, content: content, timestamp: timestamp, isImage: isImage, - isMine: userId == currentUserId, chatDateInfo: timestamp.toChatDateInfo() + isMine: userId == currentUserId, chatDateInfo: timestamp.toLostItemChatDateInfo() ) } } - diff --git a/Koin/Data/DTOs/Decodable/Chat/ChatRoomDto.swift b/Koin/Data/DTOs/Decodable/LostItem/LostItemChat/LostItemChatRoomDto.swift similarity index 81% rename from Koin/Data/DTOs/Decodable/Chat/ChatRoomDto.swift rename to Koin/Data/DTOs/Decodable/LostItem/LostItemChat/LostItemChatRoomDto.swift index 151bfb231..ca1c2276c 100644 --- a/Koin/Data/DTOs/Decodable/Chat/ChatRoomDto.swift +++ b/Koin/Data/DTOs/Decodable/LostItem/LostItemChat/LostItemChatRoomDto.swift @@ -1,5 +1,5 @@ // -// ChatRoomDto.swift +// LostItemChatRoomDto.swift // koin // // Created by 김나훈 on 2/18/25. @@ -7,7 +7,7 @@ import Foundation -struct ChatRoomDto: Codable { +struct LostItemChatRoomDto: Codable { let articleTitle, recentMessageContent: String let lostItemImageUrl: String? let unreadMessageCount: Int @@ -24,16 +24,16 @@ struct ChatRoomDto: Codable { case chatRoomId = "chat_room_id" } } -extension ChatRoomDto { - func toDomain() -> ChatRoomItem { +extension LostItemChatRoomDto { + func toDomain() -> LostItemChatRoomItem { let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] - return ChatRoomItem( + return LostItemChatRoomItem( articleTitle: articleTitle, recentMessageContent: recentMessageContent, lostItemImageUrl: lostItemImageUrl, unreadMessageCount: unreadMessageCount, lastMessageAt: lastMessageAt, - chatDateInfo: lastMessageAt.toChatDateInfo(), + chatDateInfo: lastMessageAt.toLostItemChatDateInfo(), articleId: articleId, chatRoomId: chatRoomId ) diff --git a/Koin/Data/DTOs/Decodable/Chat/CreateChatRoomResponse.swift b/Koin/Data/DTOs/Decodable/LostItem/LostItemChat/LostItemCreateChatRoomResponse.swift similarity index 84% rename from Koin/Data/DTOs/Decodable/Chat/CreateChatRoomResponse.swift rename to Koin/Data/DTOs/Decodable/LostItem/LostItemChat/LostItemCreateChatRoomResponse.swift index ddf3ccf81..1c54f3487 100644 --- a/Koin/Data/DTOs/Decodable/Chat/CreateChatRoomResponse.swift +++ b/Koin/Data/DTOs/Decodable/LostItem/LostItemChat/LostItemCreateChatRoomResponse.swift @@ -1,5 +1,5 @@ // -// CreateCharRoomResponse.swift +// LostItemCreateChatRoomResponse.swift // koin // // Created by 김나훈 on 2/18/25. @@ -7,7 +7,7 @@ import Foundation -struct CreateChatRoomResponse: Decodable { +struct LostItemCreateChatRoomResponse: Decodable { let articleId: Int let chatRoomId: Int let userId: Int diff --git a/Koin/Data/DTOs/Encodable/Chat/PostChatDetailRequest.swift b/Koin/Data/DTOs/Encodable/LostItem/LostItemChat/LostItemPostChatDetailRequest.swift similarity index 77% rename from Koin/Data/DTOs/Encodable/Chat/PostChatDetailRequest.swift rename to Koin/Data/DTOs/Encodable/LostItem/LostItemChat/LostItemPostChatDetailRequest.swift index b16a31eba..38dddc0dc 100644 --- a/Koin/Data/DTOs/Encodable/Chat/PostChatDetailRequest.swift +++ b/Koin/Data/DTOs/Encodable/LostItem/LostItemChat/LostItemPostChatDetailRequest.swift @@ -1,5 +1,5 @@ // -// PostChatDetailRequest.swift +// LostItemPostChatDetailRequest.swift // koin // // Created by 홍기정 on 1/28/26. @@ -7,7 +7,7 @@ import Foundation -struct PostChatDetailRequest: Encodable { +struct LostItemPostChatDetailRequest: Encodable { let userNickname: String let content: String diff --git a/Koin/Data/Repository/DefaultChatRepository.swift b/Koin/Data/Repository/DefaultChatRepository.swift deleted file mode 100644 index 48fdc3ce7..000000000 --- a/Koin/Data/Repository/DefaultChatRepository.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// DefaultChatRepository.swift -// koin -// -// Created by 김나훈 on 2/18/25. -// - -import Combine - -final class DefaultChatRepository: ChatRepository { - - private let service: ChatService - - init(service: ChatService) { - self.service = service - } - - func createChatRoom(articleId: Int) -> AnyPublisher { - service.createChatRoom(articleId: articleId) - } - - func blockUser(articleId: Int, chatRoomId: Int) -> AnyPublisher { - service.blockUser(articleId: articleId, chatRoomId: chatRoomId) - } - - func fetchChatRoom() -> AnyPublisher<[ChatRoomDto], ErrorResponse> { - service.fetchChatRoom() - } - - func fetchChatDetail(articleId: Int, chatRoomId: Int) -> AnyPublisher<[ChatDetailDto], ErrorResponse> { - service.fetchChatDetail(articleId: articleId, chatRoomId: chatRoomId) - } - - func postChatDetail(articleId: Int, chatRoomId: Int, request: PostChatDetailRequest) -> AnyPublisher { - service.postChatDetail(articleId: articleId, chatRoomId: chatRoomId, request: request) - } -} diff --git a/Koin/Data/Repository/DefaultLostItemRepository.swift b/Koin/Data/Repository/DefaultLostItemRepository.swift index cd3b8fd03..745d6915c 100644 --- a/Koin/Data/Repository/DefaultLostItemRepository.swift +++ b/Koin/Data/Repository/DefaultLostItemRepository.swift @@ -79,4 +79,24 @@ final class DefaultLostItemRepository: LostItemRepository { func unsubscribeKeyword(id: Int) -> AnyPublisher { return service.unsubscribeKeyword(id: id) } + + func createChatRoom(articleId: Int) -> AnyPublisher { + service.createChatRoom(articleId: articleId) + } + + func blockUser(articleId: Int, chatRoomId: Int) -> AnyPublisher { + service.blockUser(articleId: articleId, chatRoomId: chatRoomId) + } + + func fetchChatRoom() -> AnyPublisher<[LostItemChatRoomDto], ErrorResponse> { + service.fetchChatRoom() + } + + func fetchChatDetail(articleId: Int, chatRoomId: Int) -> AnyPublisher<[LostItemChatDetailDto], ErrorResponse> { + service.fetchChatDetail(articleId: articleId, chatRoomId: chatRoomId) + } + + func postChatDetail(articleId: Int, chatRoomId: Int, request: LostItemPostChatDetailRequest) -> AnyPublisher { + service.postChatDetail(articleId: articleId, chatRoomId: chatRoomId, request: request) + } } diff --git a/Koin/Data/Service/ChatService.swift b/Koin/Data/Service/ChatService.swift deleted file mode 100644 index ea5479bc8..000000000 --- a/Koin/Data/Service/ChatService.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// ChatService.swift -// koin -// -// Created by 김나훈 on 2/18/25. -// - -import Alamofire -import Combine - -protocol ChatService { - func fetchChatRoom() -> AnyPublisher<[ChatRoomDto], ErrorResponse> - func fetchChatDetail(articleId: Int, chatRoomId: Int) -> AnyPublisher<[ChatDetailDto], ErrorResponse> - func blockUser(articleId: Int, chatRoomId: Int) -> AnyPublisher - func createChatRoom(articleId: Int) -> AnyPublisher - func postChatDetail(articleId: Int, chatRoomId: Int, request: PostChatDetailRequest) -> AnyPublisher -} - -final class DefaultChatService: ChatService { - - private let networkService = NetworkService.shared - - func createChatRoom(articleId: Int) -> AnyPublisher { - return networkService.requestWithResponse(api: ChatAPI.createChatRoom(articleId)) - } - - func blockUser(articleId: Int, chatRoomId: Int) -> AnyPublisher { - return networkService.request(api: ChatAPI.blockUser(articleId, chatRoomId)) - } - - func fetchChatDetail(articleId: Int, chatRoomId: Int) -> AnyPublisher<[ChatDetailDto], ErrorResponse> { - return networkService.requestWithResponse(api: ChatAPI.fetchChatDetail(articleId, chatRoomId)) - } - - func fetchChatRoom() -> AnyPublisher<[ChatRoomDto], ErrorResponse> { - return networkService.requestWithResponse(api: ChatAPI.fetchChatRoom) - } - - func postChatDetail(articleId: Int, chatRoomId: Int, request: PostChatDetailRequest) -> AnyPublisher { - return networkService.requestWithResponse(api: ChatAPI.postChatDetail(articleId, chatRoomId, request)) - } -} diff --git a/Koin/Data/Service/LostItemService.swift b/Koin/Data/Service/LostItemService.swift index 3c899b506..dfe98bf3e 100644 --- a/Koin/Data/Service/LostItemService.swift +++ b/Koin/Data/Service/LostItemService.swift @@ -23,6 +23,12 @@ protocol LostItemService { func fetchKeywordSuggestion() -> AnyPublisher func fetchMyKeyword() -> AnyPublisher func unsubscribeKeyword(id: Int) -> AnyPublisher + + func fetchChatRoom() -> AnyPublisher<[LostItemChatRoomDto], ErrorResponse> + func fetchChatDetail(articleId: Int, chatRoomId: Int) -> AnyPublisher<[LostItemChatDetailDto], ErrorResponse> + func blockUser(articleId: Int, chatRoomId: Int) -> AnyPublisher + func createChatRoom(articleId: Int) -> AnyPublisher + func postChatDetail(articleId: Int, chatRoomId: Int, request: LostItemPostChatDetailRequest) -> AnyPublisher } final class DefaultLostItemService: LostItemService { @@ -76,4 +82,24 @@ final class DefaultLostItemService: LostItemService { func unsubscribeKeyword(id: Int) -> AnyPublisher { return networkService.request(api: LostItemAPI.unsubscribeKeyword(id)) } + + func createChatRoom(articleId: Int) -> AnyPublisher { + return networkService.requestWithResponse(api: LostItemAPI.createChatRoom(articleId)) + } + + func blockUser(articleId: Int, chatRoomId: Int) -> AnyPublisher { + return networkService.request(api: LostItemAPI.blockUser(articleId, chatRoomId)) + } + + func fetchChatDetail(articleId: Int, chatRoomId: Int) -> AnyPublisher<[LostItemChatDetailDto], ErrorResponse> { + return networkService.requestWithResponse(api: LostItemAPI.fetchChatDetail(articleId, chatRoomId)) + } + + func fetchChatRoom() -> AnyPublisher<[LostItemChatRoomDto], ErrorResponse> { + return networkService.requestWithResponse(api: LostItemAPI.fetchChatRoom) + } + + func postChatDetail(articleId: Int, chatRoomId: Int, request: LostItemPostChatDetailRequest) -> AnyPublisher { + return networkService.requestWithResponse(api: LostItemAPI.postChatDetail(articleId, chatRoomId, request)) + } } diff --git a/Koin/Data/Service/Network/API/ChatAPI.swift b/Koin/Data/Service/Network/API/ChatAPI.swift deleted file mode 100644 index 9f651217f..000000000 --- a/Koin/Data/Service/Network/API/ChatAPI.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// ChatAPI.swift -// koin -// -// Created by 김나훈 on 2/18/25. -// - -import Foundation -import Alamofire - -enum ChatAPI { - case fetchChatRoom - case fetchChatDetail(Int, Int) - case blockUser(Int, Int) - case createChatRoom(Int) - case postChatDetail(Int, Int, PostChatDetailRequest) -} - -extension ChatAPI: Router, URLRequestConvertible { - - public var baseURL: String { - return Bundle.main.baseUrl - } - - public var path: String { - switch self { - case .fetchChatRoom: return "/chatroom/lost-item" - case .fetchChatDetail(let articleId, let chatRoomId): return "/chatroom/lost-item/\(articleId)/\(chatRoomId)/messages" - case .blockUser(let articleId, let chatRoomId): return "/chatroom/lost-item/\(articleId)/\(chatRoomId)/block" - case .createChatRoom(let articleId): return "/chatroom/lost-item/\(articleId)" - case .postChatDetail(let articleId, let chatRoomId, _): return "/v2/chatroom/lost-item/\(articleId)/\(chatRoomId)/messages" - } - } - - public var method: Alamofire.HTTPMethod { - switch self { - case .fetchChatRoom, .fetchChatDetail: return .get - case .blockUser, .createChatRoom, .postChatDetail: return .post - } - } - - public var headers: [String: String] { - var baseHeaders: [String: String] = [:] - switch self { - case .createChatRoom, .postChatDetail: - baseHeaders["Content-Type"] = "application/json" - default: break - } - return baseHeaders - } - - public var parameters: Any? { - switch self { - case .fetchChatRoom, .fetchChatDetail, .blockUser, .createChatRoom: - return nil - case .postChatDetail(_, _, let request): - return try? JSONEncoder().encode(request) - } - } - - public var encoding: ParameterEncoding? { - switch self { - case .fetchChatRoom: return URLEncoding.default - case .postChatDetail: return JSONEncoding.default - case .fetchChatDetail: return nil - case .blockUser: return nil - case .createChatRoom: return nil - } - } -} diff --git a/Koin/Data/Service/Network/API/LostItemAPI.swift b/Koin/Data/Service/Network/API/LostItemAPI.swift index d9505603f..95c80c493 100644 --- a/Koin/Data/Service/Network/API/LostItemAPI.swift +++ b/Koin/Data/Service/Network/API/LostItemAPI.swift @@ -22,6 +22,12 @@ enum LostItemAPI { case fetchKeywordSuggestion case fetchMyKeyword case unsubscribeKeyword(Int) + + case fetchChatRoom + case fetchChatDetail(Int, Int) + case blockUser(Int, Int) + case createChatRoom(Int) + case postChatDetail(Int, Int, LostItemPostChatDetailRequest) } extension LostItemAPI: Router, URLRequestConvertible { @@ -44,6 +50,12 @@ extension LostItemAPI: Router, URLRequestConvertible { case .fetchKeywordSuggestion: return "/articles/keyword/suggestions?type=LOST_ITEM" case .fetchMyKeyword: return "/articles/keyword/me?type=LOST_ITEM" case .unsubscribeKeyword(let id): return "/articles/keyword/\(id)" + + case .fetchChatRoom: return "/chatroom/lost-item" + case .fetchChatDetail(let articleId, let chatRoomId): return "/chatroom/lost-item/\(articleId)/\(chatRoomId)/messages" + case .blockUser(let articleId, let chatRoomId): return "/chatroom/lost-item/\(articleId)/\(chatRoomId)/block" + case .createChatRoom(let articleId): return "/chatroom/lost-item/\(articleId)" + case .postChatDetail(let articleId, let chatRoomId, _): return "/v2/chatroom/lost-item/\(articleId)/\(chatRoomId)/messages" } } @@ -62,11 +74,19 @@ extension LostItemAPI: Router, URLRequestConvertible { case .fetchKeywordSuggestion: return .get case .fetchMyKeyword: return .get case .unsubscribeKeyword: return .delete + + case .fetchChatRoom, .fetchChatDetail: return .get + case .blockUser, .createChatRoom, .postChatDetail: return .post } } public var headers: [String: String] { - return [:] + switch self { + case .createChatRoom, .postChatDetail: + return ["Content-Type": "application/json"] + default: + return [:] + } } public var parameters: Any? { @@ -86,6 +106,11 @@ extension LostItemAPI: Router, URLRequestConvertible { return try? request.toDictionary() case .fetchKeywordSuggestion, .fetchMyKeyword, .unsubscribeKeyword: return nil + + case .fetchChatRoom, .fetchChatDetail, .blockUser, .createChatRoom: + return nil + case .postChatDetail(_, _, let request): + return try? JSONEncoder().encode(request) } } @@ -104,6 +129,10 @@ extension LostItemAPI: Router, URLRequestConvertible { return JSONEncoding.default case .fetchKeywordSuggestion, .fetchMyKeyword, .unsubscribeKeyword: return nil + + case .fetchChatRoom: return URLEncoding.default + case .postChatDetail: return JSONEncoding.default + case .fetchChatDetail, .blockUser, .createChatRoom: return nil } } } diff --git a/Koin/Domain/Model/Chat/ChatDateInfo.swift b/Koin/Domain/Model/LostItem/LostItemChatDateInfo.swift similarity index 81% rename from Koin/Domain/Model/Chat/ChatDateInfo.swift rename to Koin/Domain/Model/LostItem/LostItemChatDateInfo.swift index 414583520..a287b8580 100644 --- a/Koin/Domain/Model/Chat/ChatDateInfo.swift +++ b/Koin/Domain/Model/LostItem/LostItemChatDateInfo.swift @@ -1,5 +1,5 @@ // -// ChatDateInfo.swift +// LostItemChatDateInfo.swift // koin // // Created by 김나훈 on 2/20/25. @@ -7,7 +7,7 @@ import Foundation -struct ChatDateInfo { +struct LostItemChatDateInfo { let year: Int let month: Int let day: Int diff --git a/Koin/Domain/Model/Chat/ChatHistoryData.swift b/Koin/Domain/Model/LostItem/LostItemChatHistoryData.swift similarity index 65% rename from Koin/Domain/Model/Chat/ChatHistoryData.swift rename to Koin/Domain/Model/LostItem/LostItemChatHistoryData.swift index 83c4e37df..e79f75ffe 100644 --- a/Koin/Domain/Model/Chat/ChatHistoryData.swift +++ b/Koin/Domain/Model/LostItem/LostItemChatHistoryData.swift @@ -1,5 +1,5 @@ // -// ChatHistoryData.swift +// LostItemChatHistoryData.swift // koin // // Created by 김나훈 on 2/20/25. @@ -7,11 +7,11 @@ import Foundation -struct ChatMessage { +struct LostItemChatMessage { let senderNickname: String let content: String let timestamp: String let isImage: Bool let isMine: Bool - let chatDateInfo: ChatDateInfo + let chatDateInfo: LostItemChatDateInfo } diff --git a/Koin/Domain/Model/Chat/ChatRoomItem.swift b/Koin/Domain/Model/LostItem/LostItemChatRoomItem.swift similarity index 73% rename from Koin/Domain/Model/Chat/ChatRoomItem.swift rename to Koin/Domain/Model/LostItem/LostItemChatRoomItem.swift index 65d686719..210160138 100644 --- a/Koin/Domain/Model/Chat/ChatRoomItem.swift +++ b/Koin/Domain/Model/LostItem/LostItemChatRoomItem.swift @@ -1,5 +1,5 @@ // -// ChatRoomItem.swift +// LostItemChatRoomItem.swift // koin // // Created by 김나훈 on 2/18/25. @@ -7,13 +7,13 @@ import Foundation -struct ChatRoomItem { +struct LostItemChatRoomItem { let articleTitle: String let recentMessageContent: String let lostItemImageUrl: String? let unreadMessageCount: Int let lastMessageAt: String - let chatDateInfo: ChatDateInfo + let chatDateInfo: LostItemChatDateInfo let articleId: Int let chatRoomId: Int } diff --git a/Koin/Domain/Repository/ChatRepository.swift b/Koin/Domain/Repository/ChatRepository.swift deleted file mode 100644 index ea09f708c..000000000 --- a/Koin/Domain/Repository/ChatRepository.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// ChatRepository.swift -// koin -// -// Created by 김나훈 on 2/18/25. -// - -import Combine - -protocol ChatRepository { - func fetchChatRoom() -> AnyPublisher<[ChatRoomDto], ErrorResponse> - func fetchChatDetail(articleId: Int, chatRoomId: Int) -> AnyPublisher<[ChatDetailDto], ErrorResponse> - func blockUser(articleId: Int, chatRoomId: Int) -> AnyPublisher - func createChatRoom(articleId: Int) -> AnyPublisher - func postChatDetail(articleId: Int, chatRoomId: Int, request: PostChatDetailRequest) -> AnyPublisher -} diff --git a/Koin/Domain/Repository/LostItemRepository.swift b/Koin/Domain/Repository/LostItemRepository.swift index d83f33236..f01c8aff6 100644 --- a/Koin/Domain/Repository/LostItemRepository.swift +++ b/Koin/Domain/Repository/LostItemRepository.swift @@ -23,4 +23,10 @@ protocol LostItemRepository { func fetchKeywordSuggestion() -> AnyPublisher<[String], ErrorResponse> func fetchMyKeyword() -> AnyPublisher func unsubscribeKeyword(id: Int) -> AnyPublisher + + func fetchChatRoom() -> AnyPublisher<[LostItemChatRoomDto], ErrorResponse> + func fetchChatDetail(articleId: Int, chatRoomId: Int) -> AnyPublisher<[LostItemChatDetailDto], ErrorResponse> + func blockUser(articleId: Int, chatRoomId: Int) -> AnyPublisher + func createChatRoom(articleId: Int) -> AnyPublisher + func postChatDetail(articleId: Int, chatRoomId: Int, request: LostItemPostChatDetailRequest) -> AnyPublisher } diff --git a/Koin/Domain/UseCase/Chat/CreateChatRoomUseCase.swift b/Koin/Domain/UseCase/Chat/CreateChatRoomUseCase.swift deleted file mode 100644 index 9ba326756..000000000 --- a/Koin/Domain/UseCase/Chat/CreateChatRoomUseCase.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// CreateChatRoomUseCase.swift -// koin -// -// Created by 김나훈 on 2/18/25. -// - -import Combine - -protocol CreateChatRoomUseCase { - func execute(articleId: Int) -> AnyPublisher -} -final class DefaultCreateChatRoomUseCase: CreateChatRoomUseCase { - - private let chatRepository: ChatRepository - - init(chatRepository: ChatRepository) { - self.chatRepository = chatRepository - } - - func execute(articleId: Int) -> AnyPublisher { - return chatRepository.createChatRoom(articleId: articleId) - } -} - diff --git a/Koin/Domain/UseCase/Chat/FetchChatRoomUseCase.swift b/Koin/Domain/UseCase/Chat/FetchChatRoomUseCase.swift deleted file mode 100644 index 6d12bfb61..000000000 --- a/Koin/Domain/UseCase/Chat/FetchChatRoomUseCase.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// FetchChatRoomUseCase.swift -// koin -// -// Created by 김나훈 on 2/18/25. -// - -import Combine - -protocol FetchChatRoomUseCase { - func execute() -> AnyPublisher<[ChatRoomItem], ErrorResponse> -} - -final class DefaultFetchChatRoomUseCase: FetchChatRoomUseCase { - private let chatRepository: ChatRepository - - init(chatRepository: ChatRepository) { - self.chatRepository = chatRepository - } - - func execute() -> AnyPublisher<[ChatRoomItem], ErrorResponse> { - return chatRepository.fetchChatRoom() - .map { $0.map { $0.toDomain() } } - .eraseToAnyPublisher() - } -} diff --git a/Koin/Domain/UseCase/Chat/PostChatDetailUseCase.swift b/Koin/Domain/UseCase/Chat/PostChatDetailUseCase.swift deleted file mode 100644 index 04bcd8609..000000000 --- a/Koin/Domain/UseCase/Chat/PostChatDetailUseCase.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// PostChatDetailUseCase.swift -// koin -// -// Created by 홍기정 on 1/28/26. -// - -import Foundation -import Combine - -protocol PostChatDetailUseCase { - func execute(articleId: Int, chatRoomId: Int, message: String, isImage: Bool) -> AnyPublisher -} - -final class DefaultPostChatDetailUseCase: PostChatDetailUseCase { - - private let chatRepository: ChatRepository - - init(chatRepository: ChatRepository) { - self.chatRepository = chatRepository - } - - func execute(articleId: Int, chatRoomId: Int, message: String, isImage: Bool) -> AnyPublisher { - let request = PostChatDetailRequest(userNickname: UserDataManager.shared.nickname, content: message, isImage: isImage) - return chatRepository.postChatDetail(articleId: articleId, chatRoomId: chatRoomId, request: request) - } -} diff --git a/Koin/Domain/UseCase/Chat/BlockUserUseCase.swift b/Koin/Domain/UseCase/LostItem/LostItemBlockUserUseCase.swift similarity index 62% rename from Koin/Domain/UseCase/Chat/BlockUserUseCase.swift rename to Koin/Domain/UseCase/LostItem/LostItemBlockUserUseCase.swift index 9eae03039..e5f16862d 100644 --- a/Koin/Domain/UseCase/Chat/BlockUserUseCase.swift +++ b/Koin/Domain/UseCase/LostItem/LostItemBlockUserUseCase.swift @@ -1,5 +1,5 @@ // -// BlockUserUseCase.swift +// LostItemBlockUserUseCase.swift // koin // // Created by 김나훈 on 2/18/25. @@ -7,14 +7,14 @@ import Combine -protocol BlockUserUseCase { +protocol LostItemBlockUserUseCase { func execute(articleId: Int, chatRoomId: Int) -> AnyPublisher } -final class DefaultBlockUserUseCase: BlockUserUseCase { +final class DefaultLostItemBlockUserUseCase: LostItemBlockUserUseCase { - private let chatRepository: ChatRepository + private let chatRepository: LostItemRepository - init(chatRepository: ChatRepository) { + init(chatRepository: LostItemRepository) { self.chatRepository = chatRepository } diff --git a/Koin/Domain/UseCase/LostItem/LostItemCreateChatRoomUseCase.swift b/Koin/Domain/UseCase/LostItem/LostItemCreateChatRoomUseCase.swift new file mode 100644 index 000000000..ddb4ab2d9 --- /dev/null +++ b/Koin/Domain/UseCase/LostItem/LostItemCreateChatRoomUseCase.swift @@ -0,0 +1,24 @@ +// +// LostItemCreateChatRoomUseCase.swift +// koin +// +// Created by 김나훈 on 2/18/25. +// + +import Combine + +protocol LostItemCreateChatRoomUseCase { + func execute(articleId: Int) -> AnyPublisher +} +final class DefaultLostItemCreateChatRoomUseCase: LostItemCreateChatRoomUseCase { + + private let chatRepository: LostItemRepository + + init(chatRepository: LostItemRepository) { + self.chatRepository = chatRepository + } + + func execute(articleId: Int) -> AnyPublisher { + return chatRepository.createChatRoom(articleId: articleId) + } +} diff --git a/Koin/Domain/UseCase/Chat/FetchChatDetailUseCase.swift b/Koin/Domain/UseCase/LostItem/LostItemFetchChatDetailUseCase.swift similarity index 57% rename from Koin/Domain/UseCase/Chat/FetchChatDetailUseCase.swift rename to Koin/Domain/UseCase/LostItem/LostItemFetchChatDetailUseCase.swift index 76732d9f0..30dc230ff 100644 --- a/Koin/Domain/UseCase/Chat/FetchChatDetailUseCase.swift +++ b/Koin/Domain/UseCase/LostItem/LostItemFetchChatDetailUseCase.swift @@ -1,5 +1,5 @@ // -// FetchChatDetailUseCase.swift +// LostItemFetchChatDetailUseCase.swift // koin // // Created by 김나훈 on 2/18/25. @@ -7,18 +7,18 @@ import Combine -protocol FetchChatDetailUseCase { - func execute(userId: Int, articleId: Int, chatRoomId: Int) -> AnyPublisher<[ChatMessage], ErrorResponse> +protocol LostItemFetchChatDetailUseCase { + func execute(userId: Int, articleId: Int, chatRoomId: Int) -> AnyPublisher<[LostItemChatMessage], ErrorResponse> } -final class DefaultFetchChatDetailUseCase: FetchChatDetailUseCase { +final class DefaultLostItemFetchChatDetailUseCase: LostItemFetchChatDetailUseCase { - private let chatRepository: ChatRepository + private let chatRepository: LostItemRepository - init(chatRepository: ChatRepository) { + init(chatRepository: LostItemRepository) { self.chatRepository = chatRepository } - func execute(userId: Int, articleId: Int, chatRoomId: Int) -> AnyPublisher<[ChatMessage], ErrorResponse> { + func execute(userId: Int, articleId: Int, chatRoomId: Int) -> AnyPublisher<[LostItemChatMessage], ErrorResponse> { return chatRepository.fetchChatDetail(articleId: articleId, chatRoomId: chatRoomId) .map { dtos in dtos.map { $0.toDomain(currentUserId: userId) } diff --git a/Koin/Domain/UseCase/LostItem/LostItemFetchChatRoomUseCase.swift b/Koin/Domain/UseCase/LostItem/LostItemFetchChatRoomUseCase.swift new file mode 100644 index 000000000..77dd1e0a6 --- /dev/null +++ b/Koin/Domain/UseCase/LostItem/LostItemFetchChatRoomUseCase.swift @@ -0,0 +1,26 @@ +// +// LostItemFetchChatRoomUseCase.swift +// koin +// +// Created by 김나훈 on 2/18/25. +// + +import Combine + +protocol LostItemFetchChatRoomUseCase { + func execute() -> AnyPublisher<[LostItemChatRoomItem], ErrorResponse> +} + +final class DefaultLostItemFetchChatRoomUseCase: LostItemFetchChatRoomUseCase { + private let chatRepository: LostItemRepository + + init(chatRepository: LostItemRepository) { + self.chatRepository = chatRepository + } + + func execute() -> AnyPublisher<[LostItemChatRoomItem], ErrorResponse> { + return chatRepository.fetchChatRoom() + .map { $0.map { $0.toDomain() } } + .eraseToAnyPublisher() + } +} diff --git a/Koin/Domain/UseCase/LostItem/LostItemPostChatDetailUseCase.swift b/Koin/Domain/UseCase/LostItem/LostItemPostChatDetailUseCase.swift new file mode 100644 index 000000000..5264051a4 --- /dev/null +++ b/Koin/Domain/UseCase/LostItem/LostItemPostChatDetailUseCase.swift @@ -0,0 +1,27 @@ +// +// LostItemPostChatDetailUseCase.swift +// koin +// +// Created by 홍기정 on 1/28/26. +// + +import Foundation +import Combine + +protocol LostItemPostChatDetailUseCase { + func execute(articleId: Int, chatRoomId: Int, message: String, isImage: Bool) -> AnyPublisher +} + +final class DefaultLostItemPostChatDetailUseCase: LostItemPostChatDetailUseCase { + + private let chatRepository: LostItemRepository + + init(chatRepository: LostItemRepository) { + self.chatRepository = chatRepository + } + + func execute(articleId: Int, chatRoomId: Int, message: String, isImage: Bool) -> AnyPublisher { + let request = LostItemPostChatDetailRequest(userNickname: UserDataManager.shared.nickname, content: message, isImage: isImage) + return chatRepository.postChatDetail(articleId: articleId, chatRoomId: chatRoomId, request: request) + } +} diff --git a/Koin/Presentation/Home/Category/CategoryHostingController.swift b/Koin/Presentation/Home/Category/CategoryHostingController.swift index 5f13f4cd9..1a39c77a3 100644 --- a/Koin/Presentation/Home/Category/CategoryHostingController.swift +++ b/Koin/Presentation/Home/Category/CategoryHostingController.swift @@ -186,7 +186,7 @@ extension CategoryHostingController { } private func makeChatListViewController() -> UIViewController { - return ChatListTableViewController(viewModel: ChatListTableViewModel()) + return LostItemChatListTableViewController(viewModel: LostItemChatListTableViewModel()) } private func makeLandViewController() -> UIViewController { diff --git a/Koin/Presentation/Home/Home/HomeHostingController.swift b/Koin/Presentation/Home/Home/HomeHostingController.swift index 4ecac938e..a9e36210e 100644 --- a/Koin/Presentation/Home/Home/HomeHostingController.swift +++ b/Koin/Presentation/Home/Home/HomeHostingController.swift @@ -243,7 +243,7 @@ extension HomeHostingController { return } navigationController?.pushViewController( - ChatListTableViewController(viewModel: ChatListTableViewModel()), + LostItemChatListTableViewController(viewModel: LostItemChatListTableViewModel()), animated: true ) case "lostitem": diff --git a/Koin/Presentation/Home/Notification/NotificationViewController.swift b/Koin/Presentation/Home/Notification/NotificationViewController.swift index 9e48cf44d..135430e86 100644 --- a/Koin/Presentation/Home/Notification/NotificationViewController.swift +++ b/Koin/Presentation/Home/Notification/NotificationViewController.swift @@ -195,8 +195,8 @@ extension NotificationViewController { } private func navigateToChat(articleId: Int, chatRoomId: Int) { - let viewModel = ChatViewModel(articleId: articleId, chatRoomId: chatRoomId, articleTitle: nil) - let viewController = ChatViewController(viewModel: viewModel) + let viewModel = LostItemChatViewModel(articleId: articleId, chatRoomId: chatRoomId, articleTitle: nil) + let viewController = LostItemChatViewController(viewModel: viewModel) navigationController?.pushViewController(viewController, animated: true) } @@ -213,13 +213,13 @@ extension NotificationViewController { private func navigateToLostItemData(lostItemId: Int) { let userRepository = DefaultUserRepository(service: DefaultUserService()) let lostItemRepository = DefaultLostItemRepository(service: DefaultLostItemService()) - let chatRepository = DefaultChatRepository(service: DefaultChatService()) + let chatRepository = DefaultLostItemRepository(service: DefaultLostItemService()) let checkLoginUseCase = DefaultCheckLoginUseCase(userRepository: userRepository) let fetchLostItemDataUseCase = DefaultFetchLostItemDataUseCase(repository: lostItemRepository) let fetchLostItemListUseCase = DefaultFetchLostItemListUseCase(repository: lostItemRepository) let changeLostItemStateUseCase = DefaultChangeLostItemStateUseCase(repository: lostItemRepository) let deleteLostItemUseCase = DefaultDeleteLostItemUseCase(repository: lostItemRepository) - let createChatRoomUseCase = DefaultCreateChatRoomUseCase(chatRepository: chatRepository) + let createChatRoomUseCase = DefaultLostItemCreateChatRoomUseCase(chatRepository: chatRepository) let logAnalyticsEventUseCase = DefaultLogAnalyticsEventUseCase(repository: GA4AnalyticsRepository(service: GA4AnalyticsService())) let viewModel = LostItemDataViewModel( checkLoginUseCase: checkLoginUseCase, diff --git a/Koin/Presentation/Chat/Chat/BlockCheckModalViewController.swift b/Koin/Presentation/LostItem/LostItemChat/LostItemBlockCheckModalViewController.swift similarity index 93% rename from Koin/Presentation/Chat/Chat/BlockCheckModalViewController.swift rename to Koin/Presentation/LostItem/LostItemChat/LostItemBlockCheckModalViewController.swift index 927726fdb..9483bf701 100644 --- a/Koin/Presentation/Chat/Chat/BlockCheckModalViewController.swift +++ b/Koin/Presentation/LostItem/LostItemChat/LostItemBlockCheckModalViewController.swift @@ -1,5 +1,5 @@ // -// BlockCheckModalViewController.swift +// LostItemBlockCheckModalViewController.swift // koin // // Created by 김나훈 on 2/18/25. @@ -7,7 +7,7 @@ import UIKit -final class BlockCheckModalViewController: UIViewController { +final class LostItemBlockCheckModalViewController: UIViewController { private let onBlockButtonTapped: () -> Void private let blockButton = UIButton().then { @@ -61,7 +61,7 @@ final class BlockCheckModalViewController: UIViewController { } -extension BlockCheckModalViewController { +extension LostItemBlockCheckModalViewController { private func setUpLayOuts() { [blockButton].forEach { diff --git a/Koin/Presentation/Chat/Chat/ChatHistoryTableView/ChatDateHeaderView.swift b/Koin/Presentation/LostItem/LostItemChat/LostItemChatHistoryTableView/LostItemChatDateHeaderView.swift similarity index 84% rename from Koin/Presentation/Chat/Chat/ChatHistoryTableView/ChatDateHeaderView.swift rename to Koin/Presentation/LostItem/LostItemChat/LostItemChatHistoryTableView/LostItemChatDateHeaderView.swift index dd1271c9e..5426efcfc 100644 --- a/Koin/Presentation/Chat/Chat/ChatHistoryTableView/ChatDateHeaderView.swift +++ b/Koin/Presentation/LostItem/LostItemChat/LostItemChatHistoryTableView/LostItemChatDateHeaderView.swift @@ -1,5 +1,5 @@ // -// ChatDateHeaderView.swift +// LostItemChatDateHeaderView.swift // koin // // Created by 김나훈 on 2/20/25. @@ -7,7 +7,7 @@ import UIKit -final class ChatDateHeaderView: UITableViewHeaderFooterView { +final class LostItemChatDateHeaderView: UITableViewHeaderFooterView { // MARK: - UI Components @@ -34,13 +34,13 @@ final class ChatDateHeaderView: UITableViewHeaderFooterView { } -extension ChatDateHeaderView { - func configure(date: ChatDateInfo) { +extension LostItemChatDateHeaderView { + func configure(date: LostItemChatDateInfo) { dateLabel.text = "\(date.year)년 \(date.month)월 \(date.day)일" } } -extension ChatDateHeaderView { +extension LostItemChatDateHeaderView { private func setUpLayouts() { contentView.addSubview(dateLabel) diff --git a/Koin/Presentation/Chat/Chat/ChatHistoryTableView/ChatHistoryTableView.swift b/Koin/Presentation/LostItem/LostItemChat/LostItemChatHistoryTableView/LostItemChatHistoryTableView.swift similarity index 77% rename from Koin/Presentation/Chat/Chat/ChatHistoryTableView/ChatHistoryTableView.swift rename to Koin/Presentation/LostItem/LostItemChat/LostItemChatHistoryTableView/LostItemChatHistoryTableView.swift index 24b2e17da..e762d84cf 100644 --- a/Koin/Presentation/Chat/Chat/ChatHistoryTableView/ChatHistoryTableView.swift +++ b/Koin/Presentation/LostItem/LostItemChat/LostItemChatHistoryTableView/LostItemChatHistoryTableView.swift @@ -1,5 +1,5 @@ // -// ChatHistoryTableView.swift +// LostItemChatHistoryTableView.swift // koin // // Created by 김나훈 on 2/20/25. @@ -8,10 +8,10 @@ import Combine import UIKit -final class ChatHistoryTableView: UITableView { +final class LostItemChatHistoryTableView: UITableView { // MARK: - Properties - private var chatSections: [(date: ChatDateInfo, messages: [ChatMessage])] = [] + private var chatSections: [(date: LostItemChatDateInfo, messages: [LostItemChatMessage])] = [] let imageTapPublisher = PassthroughSubject() // MARK: - Initialization @@ -30,9 +30,9 @@ final class ChatHistoryTableView: UITableView { dataSource = self sectionHeaderTopPadding = 0 separatorStyle = .none - register(ChatImageTableViewCell.self, forCellReuseIdentifier: "ChatImageTableViewCell") - register(ChatTextTableViewCell.self, forCellReuseIdentifier: ChatTextTableViewCell.identifier) - register(ChatDateHeaderView.self, forHeaderFooterViewReuseIdentifier: ChatDateHeaderView.identifier) + register(LostItemChatImageTableViewCell.self, forCellReuseIdentifier: LostItemChatImageTableViewCell.identifier) + register(LostItemChatTextTableViewCell.self, forCellReuseIdentifier: LostItemChatTextTableViewCell.identifier) + register(LostItemChatDateHeaderView.self, forHeaderFooterViewReuseIdentifier: LostItemChatDateHeaderView.identifier) } override func layoutSubviews() { super.layoutSubviews() @@ -47,14 +47,14 @@ final class ChatHistoryTableView: UITableView { } // MARK: - 데이터 세팅 - func setChatHistory(item: [ChatMessage]) { + func setChatHistory(item: [LostItemChatMessage]) { chatSections = groupMessagesByDate(messages: item) reloadData() scrollToBottom(animated: false) } - private func groupMessagesByDate(messages: [ChatMessage]) -> [(date: ChatDateInfo, messages: [ChatMessage])] { - var groupedMessages: [(date: ChatDateInfo, messages: [ChatMessage])] = [] + private func groupMessagesByDate(messages: [LostItemChatMessage]) -> [(date: LostItemChatDateInfo, messages: [LostItemChatMessage])] { + var groupedMessages: [(date: LostItemChatDateInfo, messages: [LostItemChatMessage])] = [] for message in messages { if let lastSection = groupedMessages.last, lastSection.date.day == message.chatDateInfo.day { @@ -69,7 +69,7 @@ final class ChatHistoryTableView: UITableView { return groupedMessages } - func appendNewMessage(_ message: ChatMessage) { + func appendNewMessage(_ message: LostItemChatMessage) { if let lastSection = chatSections.last, lastSection.date.day == message.chatDateInfo.day { // 같은 날짜(day)면 기존 섹션에 메시지 추가 chatSections[chatSections.count - 1].messages.append(message) @@ -88,7 +88,7 @@ final class ChatHistoryTableView: UITableView { } // MARK: - UITableViewDataSource -extension ChatHistoryTableView: UITableViewDataSource { +extension LostItemChatHistoryTableView: UITableViewDataSource { private func scrollToBottom(animated: Bool) { guard !chatSections.isEmpty else { return } @@ -115,7 +115,7 @@ extension ChatHistoryTableView: UITableViewDataSource { let message = chatSections[indexPath.section].messages[indexPath.row] if message.isImage { - guard let cell = tableView.dequeueReusableCell(withIdentifier: "ChatImageTableViewCell", for: indexPath) as? ChatImageTableViewCell else { + guard let cell = tableView.dequeueReusableCell(withIdentifier: LostItemChatImageTableViewCell.identifier, for: indexPath) as? LostItemChatImageTableViewCell else { return UITableViewCell() } cell.configure(message: message) @@ -124,7 +124,7 @@ extension ChatHistoryTableView: UITableViewDataSource { }.store(in: &cell.cancellables) return cell } else { - guard let cell = tableView.dequeueReusableCell(withIdentifier: ChatTextTableViewCell.identifier, for: indexPath) as? ChatTextTableViewCell else { + guard let cell = tableView.dequeueReusableCell(withIdentifier: LostItemChatTextTableViewCell.identifier, for: indexPath) as? LostItemChatTextTableViewCell else { return UITableViewCell() } cell.configure(message: message) @@ -134,10 +134,10 @@ extension ChatHistoryTableView: UITableViewDataSource { } // MARK: - UITableViewDelegate (헤더) -extension ChatHistoryTableView: UITableViewDelegate { +extension LostItemChatHistoryTableView: UITableViewDelegate { func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - guard let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: ChatDateHeaderView.identifier) as? ChatDateHeaderView else { + guard let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: LostItemChatDateHeaderView.identifier) as? LostItemChatDateHeaderView else { return nil } header.configure(date: chatSections[section].date) diff --git a/Koin/Presentation/Chat/Chat/ChatHistoryTableView/ChatImageTableViewCell.swift b/Koin/Presentation/LostItem/LostItemChat/LostItemChatHistoryTableView/LostItemChatImageTableViewCell.swift similarity index 91% rename from Koin/Presentation/Chat/Chat/ChatHistoryTableView/ChatImageTableViewCell.swift rename to Koin/Presentation/LostItem/LostItemChat/LostItemChatHistoryTableView/LostItemChatImageTableViewCell.swift index 5a8386172..bfdaf8eb1 100644 --- a/Koin/Presentation/Chat/Chat/ChatHistoryTableView/ChatImageTableViewCell.swift +++ b/Koin/Presentation/LostItem/LostItemChat/LostItemChatHistoryTableView/LostItemChatImageTableViewCell.swift @@ -1,5 +1,5 @@ // -// ChatImageTableViewCell.swift +// LostItemChatImageTableViewCell.swift // koin // // Created by 김나훈 on 2/20/25. @@ -8,7 +8,7 @@ import Combine import UIKit -final class ChatImageTableViewCell: UITableViewCell { +final class LostItemChatImageTableViewCell: UITableViewCell { // MARK: - Properties private var imageUrl: String? @@ -43,7 +43,7 @@ final class ChatImageTableViewCell: UITableViewCell { configureView() } - func configure(message: ChatMessage) { + func configure(message: LostItemChatMessage) { imageUrl = message.content textImageView.loadImageWithSpinner(from: message.content) timestampLabel.text = String(format: "%02d:%02d", message.chatDateInfo.hour, message.chatDateInfo.minute) @@ -60,14 +60,14 @@ final class ChatImageTableViewCell: UITableViewCell { } } -extension ChatImageTableViewCell { +extension LostItemChatImageTableViewCell { private func setUpLayouts() { [textImageView, timestampLabel].forEach { contentView.addSubview($0) } } - private func setUpConstraints(message: ChatMessage) { + private func setUpConstraints(message: LostItemChatMessage) { textImageView.snp.remakeConstraints { if message.isMine { $0.trailing.equalToSuperview().offset(-16) diff --git a/Koin/Presentation/Chat/Chat/ChatHistoryTableView/ChatTextTableViewCell.swift b/Koin/Presentation/LostItem/LostItemChat/LostItemChatHistoryTableView/LostItemChatTextTableViewCell.swift similarity index 90% rename from Koin/Presentation/Chat/Chat/ChatHistoryTableView/ChatTextTableViewCell.swift rename to Koin/Presentation/LostItem/LostItemChat/LostItemChatHistoryTableView/LostItemChatTextTableViewCell.swift index e034f1dba..d05c152ef 100644 --- a/Koin/Presentation/Chat/Chat/ChatHistoryTableView/ChatTextTableViewCell.swift +++ b/Koin/Presentation/LostItem/LostItemChat/LostItemChatHistoryTableView/LostItemChatTextTableViewCell.swift @@ -1,5 +1,5 @@ // -// ChatTextTableViewCell.swift +// LostItemChatTextTableViewCell.swift // koin // // Created by 김나훈 on 2/20/25. @@ -7,7 +7,7 @@ import UIKit -final class ChatTextTableViewCell: UITableViewCell { +final class LostItemChatTextTableViewCell: UITableViewCell { // MARK: - UI Components private let messageLabel = InsetLabel(top: 10, left: 12, bottom: 10, right: 12).then { @@ -37,7 +37,7 @@ final class ChatTextTableViewCell: UITableViewCell { configureView() } - func configure(message: ChatMessage) { + func configure(message: LostItemChatMessage) { messageLabel.text = message.content messageLabel.backgroundColor = message.isMine ? UIColor.appColor(.neutral100) : UIColor.appColor(.info100) timestampLabel.text = String(format: "%02d:%02d", message.chatDateInfo.hour, message.chatDateInfo.minute) @@ -46,14 +46,14 @@ final class ChatTextTableViewCell: UITableViewCell { } -extension ChatTextTableViewCell { +extension LostItemChatTextTableViewCell { private func setUpLayouts() { [messageLabel, timestampLabel].forEach { contentView.addSubview($0) } } - private func setUpConstraints(message: ChatMessage) { + private func setUpConstraints(message: LostItemChatMessage) { messageLabel.snp.remakeConstraints { if message.isMine { $0.trailing.equalToSuperview().offset(-16) diff --git a/Koin/Presentation/Chat/Chat/ChatViewController.swift b/Koin/Presentation/LostItem/LostItemChat/LostItemChatViewController.swift similarity index 94% rename from Koin/Presentation/Chat/Chat/ChatViewController.swift rename to Koin/Presentation/LostItem/LostItemChat/LostItemChatViewController.swift index b30a46f5b..14f1e93cd 100644 --- a/Koin/Presentation/Chat/Chat/ChatViewController.swift +++ b/Koin/Presentation/LostItem/LostItemChat/LostItemChatViewController.swift @@ -1,5 +1,5 @@ // -// ChatViewController.swift +// LostItemChatViewController.swift // koin // // Created by 김나훈 on 2/16/25. @@ -9,11 +9,11 @@ import Combine import PhotosUI import UIKit -final class ChatViewController: UIViewController, UITextViewDelegate, PHPickerViewControllerDelegate { +final class LostItemChatViewController: UIViewController, UITextViewDelegate, PHPickerViewControllerDelegate { // // MARK: - Properties - private let viewModel: ChatViewModel - private let inputSubject: PassthroughSubject = .init() + private let viewModel: LostItemChatViewModel + private let inputSubject: PassthroughSubject = .init() private var subscriptions: Set = [] private var messageInputBottomConstraint: NSLayoutConstraint! private var textViewHeightConstraint: NSLayoutConstraint! @@ -44,11 +44,11 @@ final class ChatViewController: UIViewController, UITextViewDelegate, PHPickerVi $0.setImage(UIImage.appImage(asset: .send), for: .normal) } - private let chatHistoryTableView = ChatHistoryTableView().then { + private let chatHistoryTableView = LostItemChatHistoryTableView().then { $0.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 16, right: 0) } - init(viewModel: ChatViewModel) { + init(viewModel: LostItemChatViewModel) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) let rightButton = UIBarButtonItem(image: UIImage.appImage(asset: .threeCircle), style: .plain, target: self, action: #selector(rightButtonTapped)) @@ -116,7 +116,7 @@ final class ChatViewController: UIViewController, UITextViewDelegate, PHPickerVi } } -extension ChatViewController{ +extension LostItemChatViewController{ @objc private func sendButtonTapped() { if textView.text.isEmpty || textView.textColor == .appColor(.neutral500) { return } @@ -164,7 +164,7 @@ extension ChatViewController{ @objc private func rightButtonTapped() { dismissKeyboard() - let blockCheckModalViewController = BlockCheckModalViewController(onBlockButtonTapped: { [weak self] in + let blockCheckModalViewController = LostItemBlockCheckModalViewController(onBlockButtonTapped: { [weak self] in self?.presentBlockUserConfirmationModal() }) blockCheckModalViewController.modalPresentationStyle = .overFullScreen @@ -252,7 +252,7 @@ extension ChatViewController{ } } -extension ChatViewController { +extension LostItemChatViewController { private func setUpLayOuts() { [bottomBackgroundView, chatHistoryTableView, messageInputView].forEach { diff --git a/Koin/Presentation/Chat/Chat/ChatViewModel.swift b/Koin/Presentation/LostItem/LostItemChat/LostItemChatViewModel.swift similarity index 85% rename from Koin/Presentation/Chat/Chat/ChatViewModel.swift rename to Koin/Presentation/LostItem/LostItemChat/LostItemChatViewModel.swift index 82b61e6c7..3e4a0b578 100644 --- a/Koin/Presentation/Chat/Chat/ChatViewModel.swift +++ b/Koin/Presentation/LostItem/LostItemChat/LostItemChatViewModel.swift @@ -1,5 +1,5 @@ // -// ChatViewModel.swift +// LostItemChatViewModel.swift // koin // // Created by 김나훈 on 2/16/25. @@ -8,7 +8,7 @@ import Combine import Foundation -final class ChatViewModel: ViewModelProtocol { +final class LostItemChatViewModel: ViewModelProtocol { // MARK: - Input @@ -25,7 +25,7 @@ final class ChatViewModel: ViewModelProtocol { enum Output { case updateTitle(String) - case showChatHistory([ChatMessage]) + case showChatHistory([LostItemChatMessage]) case showToast(String, Bool) } @@ -33,13 +33,13 @@ final class ChatViewModel: ViewModelProtocol { private let outputSubject = PassthroughSubject() private var subscriptions: Set = [] private var pollingSubscriptions: AnyCancellable? - private let chatRepository = DefaultChatRepository(service: DefaultChatService()) - private lazy var fetchChatDetailUseCase = DefaultFetchChatDetailUseCase(chatRepository: chatRepository) - private lazy var blockUserUserCase = DefaultBlockUserUseCase(chatRepository: chatRepository) - private lazy var postChatDetailUseCase = DefaultPostChatDetailUseCase(chatRepository: chatRepository) + private let chatRepository = DefaultLostItemRepository(service: DefaultLostItemService()) + private lazy var fetchChatDetailUseCase = DefaultLostItemFetchChatDetailUseCase(chatRepository: chatRepository) + private lazy var blockUserUserCase = DefaultLostItemBlockUserUseCase(chatRepository: chatRepository) + private lazy var postChatDetailUseCase = DefaultLostItemPostChatDetailUseCase(chatRepository: chatRepository) private let fetchUserDataUseCase = DefaultFetchUserDataUseCase(userRepository: DefaultUserRepository(service: DefaultUserService())) private lazy var uploadFileUseCase = DefaultUploadFileUseCase(coreRepository: DefaultCoreRepository(service: DefaultCoreService())) - private lazy var fetchChatRoomUseCase = DefaultFetchChatRoomUseCase(chatRepository: chatRepository) + private lazy var fetchChatRoomUseCase = DefaultLostItemFetchChatRoomUseCase(chatRepository: chatRepository) let articleId: Int let chatRoomId: Int private var articleTitle: String? @@ -76,7 +76,7 @@ final class ChatViewModel: ViewModelProtocol { } -extension ChatViewModel { +extension LostItemChatViewModel { private func uploadFiles(files: [Data]) { uploadFileUseCase.execute(files: files, domain: .lostItem).sink { [weak self] completion in @@ -106,10 +106,10 @@ extension ChatViewModel { pollingSubscriptions = Timer.publish(every: 1, on: .main, in: .common) .autoconnect() .prepend(Date()) - .flatMap { [weak self] _ -> AnyPublisher<[ChatMessage], Never> in + .flatMap { [weak self] _ -> AnyPublisher<[LostItemChatMessage], Never> in guard let self else { return Empty().eraseToAnyPublisher() } return fetchChatDetailUseCase.execute(userId: UserDataManager.shared.id, articleId: articleId, chatRoomId: chatRoomId) - .catch { error -> AnyPublisher<[ChatMessage], Never> in + .catch { error -> AnyPublisher<[LostItemChatMessage], Never> in return Empty().eraseToAnyPublisher() } .eraseToAnyPublisher() diff --git a/Koin/Presentation/Chat/ChatList/ChatListTableViewController.swift b/Koin/Presentation/LostItem/LostItemChatList/LostItemChatListTableViewController.swift similarity index 89% rename from Koin/Presentation/Chat/ChatList/ChatListTableViewController.swift rename to Koin/Presentation/LostItem/LostItemChatList/LostItemChatListTableViewController.swift index 5f0d88f2e..e26279f82 100644 --- a/Koin/Presentation/Chat/ChatList/ChatListTableViewController.swift +++ b/Koin/Presentation/LostItem/LostItemChatList/LostItemChatListTableViewController.swift @@ -1,5 +1,5 @@ // -// ChatListTableViewController.swift +// LostItemChatListTableViewController.swift // koin // // Created by 김나훈 on 2/18/25. @@ -8,17 +8,17 @@ import Combine import UIKit -final class ChatListTableViewController: UITableViewController { +final class LostItemChatListTableViewController: UITableViewController { // MARK: - Properties - private let viewModel: ChatListTableViewModel - private let inputSubject: PassthroughSubject = .init() + private let viewModel: LostItemChatListTableViewModel + private let inputSubject: PassthroughSubject = .init() private var subscriptions: Set = [] // MARK: - UI Components - init(viewModel: ChatListTableViewModel) { + init(viewModel: LostItemChatListTableViewModel) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) navigationItem.title = "쪽지" @@ -35,7 +35,7 @@ final class ChatListTableViewController: UITableViewController { super.viewDidLoad() configureView() bind() - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "ChatCell") + tableView.register(UITableViewCell.self, forCellReuseIdentifier: UITableViewCell.identifier) tableView.separatorStyle = .none } @@ -65,11 +65,11 @@ final class ChatListTableViewController: UITableViewController { } } -extension ChatListTableViewController { +extension LostItemChatListTableViewController { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let chat = viewModel.chatList[indexPath.row] - let viewController = ChatViewController(viewModel: ChatViewModel(articleId: chat.articleId, chatRoomId: chat.chatRoomId, articleTitle: chat.articleTitle)) + let viewController = LostItemChatViewController(viewModel: LostItemChatViewModel(articleId: chat.articleId, chatRoomId: chat.chatRoomId, articleTitle: chat.articleTitle)) inputSubject.send(.logEvent(EventParameter.EventLabel.Campus.messageListSelect, .click, "쪽지")) navigationController?.pushViewController(viewController, animated: true) } @@ -86,7 +86,7 @@ extension ChatListTableViewController { } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "ChatCell", for: indexPath) + let cell = tableView.dequeueReusableCell(withIdentifier: UITableViewCell.identifier, for: indexPath) let chat = viewModel.chatList[indexPath.row] cell.contentView.subviews.forEach { $0.removeFromSuperview() } let thumbnailContainerView = UIView().then { @@ -138,7 +138,7 @@ extension ChatListTableViewController { } titleLabel.text = chat.articleTitle contentLabel.text = "\(chat.recentMessageContent)" - recentTimeLabel.text = chat.lastMessageAt.toChatDateInfo().showingText + recentTimeLabel.text = chat.lastMessageAt.toLostItemChatDateInfo().showingText unreadMessageLabel.text = String(chat.unreadMessageCount) unreadMessageLabel.isHidden = chat.unreadMessageCount == 0 @@ -185,7 +185,7 @@ extension ChatListTableViewController { } -extension ChatListTableViewController { +extension LostItemChatListTableViewController { private func setUpLayOuts() { [].forEach { diff --git a/Koin/Presentation/Chat/ChatList/ChatListTableViewModel.swift b/Koin/Presentation/LostItem/LostItemChatList/LostItemChatListTableViewModel.swift similarity index 79% rename from Koin/Presentation/Chat/ChatList/ChatListTableViewModel.swift rename to Koin/Presentation/LostItem/LostItemChatList/LostItemChatListTableViewModel.swift index 571a59951..3aa4f6671 100644 --- a/Koin/Presentation/Chat/ChatList/ChatListTableViewModel.swift +++ b/Koin/Presentation/LostItem/LostItemChatList/LostItemChatListTableViewModel.swift @@ -1,5 +1,5 @@ // -// ChatListTableViewModel.swift +// LostItemChatListTableViewModel.swift // koin // // Created by 김나훈 on 2/18/25. @@ -8,7 +8,7 @@ import Combine import Foundation -final class ChatListTableViewModel: ViewModelProtocol { +final class LostItemChatListTableViewModel: ViewModelProtocol { // MARK: - Input @@ -28,19 +28,18 @@ final class ChatListTableViewModel: ViewModelProtocol { private let outputSubject = PassthroughSubject() private var subscriptions: Set = [] private var pollingSubscriptions: AnyCancellable? - private(set) var chatList: [ChatRoomItem] = [] { + private(set) var chatList: [LostItemChatRoomItem] = [] { didSet { outputSubject.send(.showChatRoom) } } - private let chatRepository = DefaultChatRepository(service: DefaultChatService()) - private lazy var fetchChatRoomUseCase = DefaultFetchChatRoomUseCase(chatRepository: chatRepository) + private let chatRepository = DefaultLostItemRepository(service: DefaultLostItemService()) + private lazy var fetchChatRoomUseCase = DefaultLostItemFetchChatRoomUseCase(chatRepository: chatRepository) private let logAnalyticsEventUseCase: LogAnalyticsEventUseCase = DefaultLogAnalyticsEventUseCase(repository: GA4AnalyticsRepository(service: GA4AnalyticsService())) // MARK: - Initialization - init() { - } + init() {} func transform(with input: AnyPublisher) -> AnyPublisher { input.sink { [weak self] input in @@ -60,17 +59,17 @@ final class ChatListTableViewModel: ViewModelProtocol { } -extension ChatListTableViewModel { +extension LostItemChatListTableViewModel { private func fetchChatRooms() { pollingSubscriptions?.cancel() pollingSubscriptions = Timer.publish(every: 1, on: .main, in: .common) .autoconnect() .prepend(Date()) - .flatMap { [weak self] _ -> AnyPublisher<[ChatRoomItem], Never> in + .flatMap { [weak self] _ -> AnyPublisher<[LostItemChatRoomItem], Never> in guard let self else { return Empty().eraseToAnyPublisher() } return fetchChatRoomUseCase.execute() - .catch { error -> AnyPublisher<[ChatRoomItem], Never> in + .catch { error -> AnyPublisher<[LostItemChatRoomItem], Never> in return Empty().eraseToAnyPublisher() }.eraseToAnyPublisher() } diff --git a/Koin/Presentation/LostItem/LostItemData/LostItemDataViewController.swift b/Koin/Presentation/LostItem/LostItemData/LostItemDataViewController.swift index b31edbc77..4f28859ed 100644 --- a/Koin/Presentation/LostItem/LostItemData/LostItemDataViewController.swift +++ b/Koin/Presentation/LostItem/LostItemData/LostItemDataViewController.swift @@ -101,12 +101,12 @@ final class LostItemDataViewController: UIViewController { lostItemDataTableView.cellTappedPublisher.sink { [weak self] id in let userRepository = DefaultUserRepository(service: DefaultUserService()) let lostItemRepository = DefaultLostItemRepository(service: DefaultLostItemService()) - let chatRepository = DefaultChatRepository(service: DefaultChatService()) + let chatRepository = DefaultLostItemRepository(service: DefaultLostItemService()) let checkLoginUseCase = DefaultCheckLoginUseCase(userRepository: userRepository) let fetchLostItemDataUseCase = DefaultFetchLostItemDataUseCase(repository: lostItemRepository) let fetchLostItemListUseCase = DefaultFetchLostItemListUseCase(repository: lostItemRepository) let changeLostItemStateUseCase = DefaultChangeLostItemStateUseCase(repository: lostItemRepository) - let createChatRoomUseCase = DefaultCreateChatRoomUseCase(chatRepository: chatRepository) + let createChatRoomUseCase = DefaultLostItemCreateChatRoomUseCase(chatRepository: chatRepository) let deleteLostItemUseCase = DefaultDeleteLostItemUseCase(repository: lostItemRepository) let logAnalyticsEventUseCase = DefaultLogAnalyticsEventUseCase(repository: GA4AnalyticsRepository(service: GA4AnalyticsService())) let viewModel = LostItemDataViewModel( @@ -289,13 +289,13 @@ extension LostItemDataViewController { navigationController?.present(modalViewController, animated: true) } - private func navigateToChat(_ createChatRoomResponse: CreateChatRoomResponse) { - let chatViewModel = ChatViewModel( + private func navigateToChat(_ createChatRoomResponse: LostItemCreateChatRoomResponse) { + let chatViewModel = LostItemChatViewModel( articleId: createChatRoomResponse.articleId, chatRoomId: createChatRoomResponse.chatRoomId, articleTitle: createChatRoomResponse.articleTitle ) - let viewController = ChatViewController(viewModel: chatViewModel) + let viewController = LostItemChatViewController(viewModel: chatViewModel) navigationController?.pushViewController(viewController, animated: true) } diff --git a/Koin/Presentation/LostItem/LostItemData/LostItemDataViewModel.swift b/Koin/Presentation/LostItem/LostItemData/LostItemDataViewModel.swift index cbcc7cc29..12a69ea8e 100644 --- a/Koin/Presentation/LostItem/LostItemData/LostItemDataViewModel.swift +++ b/Koin/Presentation/LostItem/LostItemData/LostItemDataViewModel.swift @@ -28,7 +28,7 @@ final class LostItemDataViewModel: ViewModelProtocol { case deletedData(Int) case popViewController case checkedLogin((CheckLoginOption, Bool)) - case navigateToChat(CreateChatRoomResponse) + case navigateToChat(LostItemCreateChatRoomResponse) } enum CheckLoginOption { @@ -42,7 +42,7 @@ final class LostItemDataViewModel: ViewModelProtocol { private let fetchLostItemListUseCase: FetchLostItemListUseCase private let changeLostItemStateUseCase: ChangeLostItemStateUseCase private let deleteLostItemUseCase: DeleteLostItemUseCase - private let createChatRoomUseCase: CreateChatRoomUseCase + private let createChatRoomUseCase: LostItemCreateChatRoomUseCase private let logAnalyticsEventUseCase: LogAnalyticsEventUseCase var type: LostItemType? private let outputSubject = PassthroughSubject() @@ -57,7 +57,7 @@ final class LostItemDataViewModel: ViewModelProtocol { fetchLostItemListUseCase: FetchLostItemListUseCase, changeLostItemStateUseCase: ChangeLostItemStateUseCase, deleteLostItemUseCase: DeleteLostItemUseCase, - createChatRoomUseCase: CreateChatRoomUseCase, + createChatRoomUseCase: LostItemCreateChatRoomUseCase, logAnalyticsEventUseCase: LogAnalyticsEventUseCase, id: Int) { self.checkLoginUseCase = checkLoginUseCase diff --git a/Koin/Presentation/LostItem/LostItemList/LostItemListViewController.swift b/Koin/Presentation/LostItem/LostItemList/LostItemListViewController.swift index 729797697..99e09adcd 100644 --- a/Koin/Presentation/LostItem/LostItemList/LostItemListViewController.swift +++ b/Koin/Presentation/LostItem/LostItemList/LostItemListViewController.swift @@ -125,13 +125,13 @@ final class LostItemListViewController: UIViewController { let userRepository = DefaultUserRepository(service: DefaultUserService()) let lostItemRepository = DefaultLostItemRepository(service: DefaultLostItemService()) - let chatRepository = DefaultChatRepository(service: DefaultChatService()) + let chatRepository = DefaultLostItemRepository(service: DefaultLostItemService()) let checkLoginUseCase = DefaultCheckLoginUseCase(userRepository: userRepository) let fetchLostItemDataUseCase = DefaultFetchLostItemDataUseCase(repository: lostItemRepository) let fetchLostItemListUseCase = DefaultFetchLostItemListUseCase(repository: lostItemRepository) let changeLostItemStateUseCase = DefaultChangeLostItemStateUseCase(repository: lostItemRepository) let deleteLostItemUseCase = DefaultDeleteLostItemUseCase(repository: lostItemRepository) - let createChatRoomUseCase = DefaultCreateChatRoomUseCase(chatRepository: chatRepository) + let createChatRoomUseCase = DefaultLostItemCreateChatRoomUseCase(chatRepository: chatRepository) let logAnalyticsEventUseCase = DefaultLogAnalyticsEventUseCase(repository: GA4AnalyticsRepository(service: GA4AnalyticsService())) let viewModel = LostItemDataViewModel( checkLoginUseCase: checkLoginUseCase, diff --git a/Koin/Presentation/LostItem/PostLostItem/PostLostItemViewController.swift b/Koin/Presentation/LostItem/PostLostItem/PostLostItemViewController.swift index 92b2e4678..d89f1adef 100644 --- a/Koin/Presentation/LostItem/PostLostItem/PostLostItemViewController.swift +++ b/Koin/Presentation/LostItem/PostLostItem/PostLostItemViewController.swift @@ -244,13 +244,13 @@ extension PostLostItemViewController: UITextViewDelegate, PHPickerViewController private func navigateToLostItemData(_ id: Int) { let userRepository = DefaultUserRepository(service: DefaultUserService()) let lostItemRepository = DefaultLostItemRepository(service: DefaultLostItemService()) - let chatRepository = DefaultChatRepository(service: DefaultChatService()) + let chatRepository = DefaultLostItemRepository(service: DefaultLostItemService()) let checkLoginUseCase = DefaultCheckLoginUseCase(userRepository: userRepository) let fetchLostItemDataUseCase = DefaultFetchLostItemDataUseCase(repository: lostItemRepository) let fetchLostItemListUseCase = DefaultFetchLostItemListUseCase(repository: lostItemRepository) let changeLostItemStateUseCase = DefaultChangeLostItemStateUseCase(repository: lostItemRepository) let deleteLostItemUseCase = DefaultDeleteLostItemUseCase(repository: lostItemRepository) - let createChatRoomUseCase = DefaultCreateChatRoomUseCase(chatRepository: chatRepository) + let createChatRoomUseCase = DefaultLostItemCreateChatRoomUseCase(chatRepository: chatRepository) let logAnalyticsEventUseCase = DefaultLogAnalyticsEventUseCase(repository: GA4AnalyticsRepository(service: GA4AnalyticsService())) let viewModel = LostItemDataViewModel( checkLoginUseCase: checkLoginUseCase, diff --git a/koin.xcodeproj/project.pbxproj b/koin.xcodeproj/project.pbxproj index 45f895722..b42a99600 100644 --- a/koin.xcodeproj/project.pbxproj +++ b/koin.xcodeproj/project.pbxproj @@ -409,8 +409,8 @@ 7CC42B962F5DCEB300940CE1 /* CallVanChatDateHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC42B952F5DCEB300940CE1 /* CallVanChatDateHeaderView.swift */; }; 7CC42B982F5E812F00940CE1 /* CallVanChatLeftCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC42B972F5E812F00940CE1 /* CallVanChatLeftCell.swift */; }; 7CC42B9C2F5E834E00940CE1 /* CallVanChatRightCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC42B9B2F5E834E00940CE1 /* CallVanChatRightCell.swift */; }; - 7CCB1E4B2F29D87C00472669 /* PostChatDetailUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCB1E4A2F29D87C00472669 /* PostChatDetailUseCase.swift */; }; - 7CCB1E4E2F29DB3200472669 /* PostChatDetailRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCB1E4D2F29DB3200472669 /* PostChatDetailRequest.swift */; }; + 7CCB1E4B2F29D87C00472669 /* LostItemPostChatDetailUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCB1E4A2F29D87C00472669 /* LostItemPostChatDetailUseCase.swift */; }; + 7CCB1E4E2F29DB3200472669 /* LostItemPostChatDetailRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCB1E4D2F29DB3200472669 /* LostItemPostChatDetailRequest.swift */; }; 7CD411EA2FEB92280079467B /* HomeTabBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CD411E92FEB92280079467B /* HomeTabBarItem.swift */; }; 7CEB3B4B2FBFF4B700AF2B81 /* DeleteDeviceTokenUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CEB3B4A2FBFF4B700AF2B81 /* DeleteDeviceTokenUseCase.swift */; }; 7CED90BC2EF2D1F900457128 /* ShopSummaryTableViewHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8FD02EF2D1F900457128 /* ShopSummaryTableViewHeaderView.swift */; }; @@ -420,10 +420,10 @@ 7CED90C32EF2D1F900457128 /* ShopSummaryPriceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8FCC2EF2D1F900457128 /* ShopSummaryPriceTableViewCell.swift */; }; 7CED90C62EF2D1F900457128 /* TimetableCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED909A2EF2D1F900457128 /* TimetableCollectionViewCell.swift */; }; 7CED90CA2EF2D1F900457128 /* LandOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90B02EF2D1F900457128 /* LandOptionCollectionViewCell.swift */; }; - 7CED90CB2EF2D1F900457128 /* ChatListTableViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8EF92EF2D1F900457128 /* ChatListTableViewModel.swift */; }; + 7CED90CB2EF2D1F900457128 /* LostItemChatListTableViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8EF92EF2D1F900457128 /* LostItemChatListTableViewModel.swift */; }; 7CED90CE2EF2D1F900457128 /* ForceModifyUserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8ED42EF2D1F900457128 /* ForceModifyUserViewController.swift */; }; 7CED90CF2EF2D1F900457128 /* ChangeMyProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F0C2EF2D1F900457128 /* ChangeMyProfileViewModel.swift */; }; - 7CED90D02EF2D1F900457128 /* ChatHistoryTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8EFC2EF2D1F900457128 /* ChatHistoryTableView.swift */; }; + 7CED90D02EF2D1F900457128 /* LostItemChatHistoryTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8EFC2EF2D1F900457128 /* LostItemChatHistoryTableView.swift */; }; 7CED90D12EF2D1F900457128 /* NoticeSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F572EF2D1F900457128 /* NoticeSearchViewController.swift */; }; 7CED90D22EF2D1F900457128 /* TagCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90152EF2D1F900457128 /* TagCollectionViewCell.swift */; }; 7CED90D32EF2D1F900457128 /* LandCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90A92EF2D1F900457128 /* LandCollectionView.swift */; }; @@ -431,7 +431,7 @@ 7CED90D72EF2D1F900457128 /* RecommendedKeywordCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F5A2EF2D1F900457128 /* RecommendedKeywordCollectionView.swift */; }; 7CED90D92EF2D1F900457128 /* DiningViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90742EF2D1F900457128 /* DiningViewController.swift */; }; 7CED90DA2EF2D1F900457128 /* RecentSearchTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F532EF2D1F900457128 /* RecentSearchTableView.swift */; }; - 7CED90DB2EF2D1F900457128 /* ChatImageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8EFE2EF2D1F900457128 /* ChatImageTableViewCell.swift */; }; + 7CED90DB2EF2D1F900457128 /* LostItemChatImageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8EFE2EF2D1F900457128 /* LostItemChatImageTableViewCell.swift */; }; 7CED90DC2EF2D1F900457128 /* ShopSummaryDeliveryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8FC42EF2D1F900457128 /* ShopSummaryDeliveryButton.swift */; }; 7CED90DD2EF2D1F900457128 /* AddDirectCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90912EF2D1F900457128 /* AddDirectCollectionViewCell.swift */; }; 7CED90DF2EF2D1F900457128 /* LandDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90B72EF2D1F900457128 /* LandDetailViewModel.swift */; }; @@ -446,9 +446,9 @@ 7CED90F02EF2D1F900457128 /* RevokeModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F0B2EF2D1F900457128 /* RevokeModalViewController.swift */; }; 7CED90F12EF2D1F900457128 /* ForceUpdateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8EDF2EF2D1F900457128 /* ForceUpdateViewModel.swift */; }; 7CED90F22EF2D1F900457128 /* ShopSearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8FBF2EF2D1F900457128 /* ShopSearchViewModel.swift */; }; - 7CED90F42EF2D1F900457128 /* ChatListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8EFA2EF2D1F900457128 /* ChatListTableViewController.swift */; }; + 7CED90F42EF2D1F900457128 /* LostItemChatListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8EFA2EF2D1F900457128 /* LostItemChatListTableViewController.swift */; }; 7CED90F52EF2D1F900457128 /* DiningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90732EF2D1F900457128 /* DiningViewModel.swift */; }; - 7CED90F62EF2D1F900457128 /* ChatDateHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8EFF2EF2D1F900457128 /* ChatDateHeaderView.swift */; }; + 7CED90F62EF2D1F900457128 /* LostItemChatDateHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8EFF2EF2D1F900457128 /* LostItemChatDateHeaderView.swift */; }; 7CED90F72EF2D1F900457128 /* ReviewImageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90032EF2D1F900457128 /* ReviewImageCollectionViewCell.swift */; }; 7CED90FA2EF2D1F900457128 /* LandDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90B82EF2D1F900457128 /* LandDetailViewController.swift */; }; 7CED90FB2EF2D1F900457128 /* TabBarCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F3E2EF2D1F900457128 /* TabBarCollectionViewCell.swift */; }; @@ -461,7 +461,7 @@ 7CED91042EF2D1F900457128 /* TimetableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90822EF2D1F900457128 /* TimetableCell.swift */; }; 7CED91052EF2D1F900457128 /* SelectTypeFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F2B2EF2D1F900457128 /* SelectTypeFormViewController.swift */; }; 7CED91072EF2D1F900457128 /* AddDirectHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90922EF2D1F900457128 /* AddDirectHeaderView.swift */; }; - 7CED91082EF2D1F900457128 /* BlockCheckModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F012EF2D1F900457128 /* BlockCheckModalViewController.swift */; }; + 7CED91082EF2D1F900457128 /* LostItemBlockCheckModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F012EF2D1F900457128 /* LostItemBlockCheckModalViewController.swift */; }; 7CED91092EF2D1F900457128 /* FacilityInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90A42EF2D1F900457128 /* FacilityInfoViewController.swift */; }; 7CED910A2EF2D1F900457128 /* LeftAlignedFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90132EF2D1F900457128 /* LeftAlignedFlowLayout.swift */; }; 7CED910B2EF2D1F900457128 /* AddClassCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90952EF2D1F900457128 /* AddClassCollectionView.swift */; }; @@ -470,7 +470,7 @@ 7CED910E2EF2D1F900457128 /* ShopSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8FBE2EF2D1F900457128 /* ShopSearchViewController.swift */; }; 7CED910F2EF2D1F900457128 /* ShopCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8EC32EF2D1F900457128 /* ShopCollectionView.swift */; }; 7CED91122EF2D1F900457128 /* LectureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90882EF2D1F900457128 /* LectureView.swift */; }; - 7CED91142EF2D1F900457128 /* ChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F032EF2D1F900457128 /* ChatViewController.swift */; }; + 7CED91142EF2D1F900457128 /* LostItemChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F032EF2D1F900457128 /* LostItemChatViewController.swift */; }; 7CED91152EF2D1F900457128 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F232EF2D1F900457128 /* LoginViewModel.swift */; }; 7CED91162EF2D1F900457128 /* ShopSortOptionSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8FB52EF2D1F900457128 /* ShopSortOptionSheetViewController.swift */; }; 7CED91172EF2D1F900457128 /* TimetableText+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90A02EF2D1F900457128 /* TimetableText+.swift */; }; @@ -572,13 +572,13 @@ 7CED91BA2EF2D1F900457128 /* CertificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F0E2EF2D1F900457128 /* CertificationView.swift */; }; 7CED91BB2EF2D1F900457128 /* EnterFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F282EF2D1F900457128 /* EnterFormViewController.swift */; }; 7CED91BD2EF2D1F900457128 /* RecommendedSearchCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F512EF2D1F900457128 /* RecommendedSearchCollectionViewCell.swift */; }; - 7CED91C02EF2D1F900457128 /* ChatTextTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8EFD2EF2D1F900457128 /* ChatTextTableViewCell.swift */; }; + 7CED91C02EF2D1F900457128 /* LostItemChatTextTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8EFD2EF2D1F900457128 /* LostItemChatTextTableViewCell.swift */; }; 7CED91C12EF2D1F900457128 /* ShopViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8FB72EF2D1F900457128 /* ShopViewModel.swift */; }; 7CED91C22EF2D1F900457128 /* ReviewImageCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90022EF2D1F900457128 /* ReviewImageCollectionView.swift */; }; 7CED91C62EF2D1F900457128 /* HotArticlesNoticeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F652EF2D1F900457128 /* HotArticlesNoticeTableViewCell.swift */; }; 7CED91C92EF2D1F900457128 /* CategoryCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8FAB2EF2D1F900457128 /* CategoryCollectionView.swift */; }; 7CED91CB2EF2D1F900457128 /* ShopSummaryImagesCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8FC22EF2D1F900457128 /* ShopSummaryImagesCollectionViewCell.swift */; }; - 7CED91CD2EF2D1F900457128 /* ChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F022EF2D1F900457128 /* ChatViewModel.swift */; }; + 7CED91CD2EF2D1F900457128 /* LostItemChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F022EF2D1F900457128 /* LostItemChatViewModel.swift */; }; 7CED91CE2EF2D1F900457128 /* NotiViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F142EF2D1F900457128 /* NotiViewModel.swift */; }; 7CED91CF2EF2D1F900457128 /* ManageNoticeKeywordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED8F622EF2D1F900457128 /* ManageNoticeKeywordViewModel.swift */; }; 7CED91D22EF2D1F900457128 /* LandImageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CED90B32EF2D1F900457128 /* LandImageCollectionViewCell.swift */; }; @@ -908,9 +908,9 @@ D2A8F8BE2C4A78F20090C7A4 /* CoopShopData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A8F8BD2C4A78F20090C7A4 /* CoopShopData.swift */; }; D2A8F8C02C4BCD470090C7A4 /* ShareMenuListUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A8F8BF2C4BCD470090C7A4 /* ShareMenuListUseCase.swift */; }; D2A8F8C22C4BD36C0090C7A4 /* ShareService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A8F8C12C4BD36C0090C7A4 /* ShareService.swift */; }; - D2B2193B2D66626900EAF5B1 /* ChatHistoryData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B2193A2D66626900EAF5B1 /* ChatHistoryData.swift */; }; + D2B2193B2D66626900EAF5B1 /* LostItemChatHistoryData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B2193A2D66626900EAF5B1 /* LostItemChatHistoryData.swift */; }; D2B2193D2D66828300EAF5B1 /* WebSocketManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B2193C2D66828200EAF5B1 /* WebSocketManager.swift */; }; - D2B2193F2D66DDF900EAF5B1 /* ChatDateInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B2193E2D66DDF900EAF5B1 /* ChatDateInfo.swift */; }; + D2B2193F2D66DDF900EAF5B1 /* LostItemChatDateInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B2193E2D66DDF900EAF5B1 /* LostItemChatDateInfo.swift */; }; D2B7EBCA2D37C81B00EE46B0 /* PostLostItemRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B7EBC92D37C81B00EE46B0 /* PostLostItemRequest.swift */; }; D2B7EBDA2D382C8800EE46B0 /* UserTypeResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B7EBD92D382C8800EE46B0 /* UserTypeResponse.swift */; }; D2B7EBDC2D382D5400EE46B0 /* CheckAuthUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B7EBDB2D382D5400EE46B0 /* CheckAuthUseCase.swift */; }; @@ -931,18 +931,14 @@ D2D422722BB2A7BE00A8AF04 /* Confirmable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D422712BB2A7BE00A8AF04 /* Confirmable.swift */; }; D2D422752BB2AB3A00A8AF04 /* PasswordConfirmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D422742BB2AB3A00A8AF04 /* PasswordConfirmer.swift */; }; D2D422772BB2EA3900A8AF04 /* FindPasswordRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D422762BB2EA3900A8AF04 /* FindPasswordRequest.swift */; }; - D2D462602D63A81500C60864 /* ChatRoomDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D4625F2D63A81500C60864 /* ChatRoomDto.swift */; }; - D2D462622D63B90100C60864 /* ChatRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D462612D63B90100C60864 /* ChatRepository.swift */; }; - D2D462662D63B98F00C60864 /* FetchChatRoomUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D462652D63B98F00C60864 /* FetchChatRoomUseCase.swift */; }; - D2D4626A2D63BA5200C60864 /* ChatService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D462692D63BA5200C60864 /* ChatService.swift */; }; - D2D4626C2D63BAC600C60864 /* ChatAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D4626B2D63BAC600C60864 /* ChatAPI.swift */; }; - D2D4626E2D63BD1400C60864 /* DefaultChatRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D4626D2D63BD1400C60864 /* DefaultChatRepository.swift */; }; - D2D462712D63C5ED00C60864 /* ChatRoomItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D462702D63C5ED00C60864 /* ChatRoomItem.swift */; }; - D2D462732D63CACC00C60864 /* ChatDetailDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D462722D63CACC00C60864 /* ChatDetailDto.swift */; }; - D2D462752D63CB3E00C60864 /* FetchChatDetailUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D462742D63CB3E00C60864 /* FetchChatDetailUseCase.swift */; }; - D2D4627A2D63D4D400C60864 /* BlockUserUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D462792D63D4D400C60864 /* BlockUserUseCase.swift */; }; - D2D4627C2D6482AA00C60864 /* CreateChatRoomResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D4627B2D6482AA00C60864 /* CreateChatRoomResponse.swift */; }; - D2D4627E2D64835B00C60864 /* CreateChatRoomUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D4627D2D64835A00C60864 /* CreateChatRoomUseCase.swift */; }; + D2D462602D63A81500C60864 /* LostItemChatRoomDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D4625F2D63A81500C60864 /* LostItemChatRoomDto.swift */; }; + D2D462662D63B98F00C60864 /* LostItemFetchChatRoomUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D462652D63B98F00C60864 /* LostItemFetchChatRoomUseCase.swift */; }; + D2D462712D63C5ED00C60864 /* LostItemChatRoomItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D462702D63C5ED00C60864 /* LostItemChatRoomItem.swift */; }; + D2D462732D63CACC00C60864 /* LostItemChatDetailDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D462722D63CACC00C60864 /* LostItemChatDetailDto.swift */; }; + D2D462752D63CB3E00C60864 /* LostItemFetchChatDetailUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D462742D63CB3E00C60864 /* LostItemFetchChatDetailUseCase.swift */; }; + D2D4627A2D63D4D400C60864 /* LostItemBlockUserUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D462792D63D4D400C60864 /* LostItemBlockUserUseCase.swift */; }; + D2D4627C2D6482AA00C60864 /* LostItemCreateChatRoomResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D4627B2D6482AA00C60864 /* LostItemCreateChatRoomResponse.swift */; }; + D2D4627E2D64835B00C60864 /* LostItemCreateChatRoomUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D4627D2D64835A00C60864 /* LostItemCreateChatRoomUseCase.swift */; }; D2D7C0D02C28285600C85A85 /* LandRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D7C0CF2C28285600C85A85 /* LandRepository.swift */; }; D2D7C0D52C28298500C85A85 /* FetchLandListUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D7C0D42C28298500C85A85 /* FetchLandListUseCase.swift */; }; D2D7C0D72C28299200C85A85 /* FetchLandDetailUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D7C0D62C28299200C85A85 /* FetchLandDetailUseCase.swift */; }; @@ -1415,8 +1411,8 @@ 7CC42B952F5DCEB300940CE1 /* CallVanChatDateHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVanChatDateHeaderView.swift; sourceTree = ""; }; 7CC42B972F5E812F00940CE1 /* CallVanChatLeftCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVanChatLeftCell.swift; sourceTree = ""; }; 7CC42B9B2F5E834E00940CE1 /* CallVanChatRightCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVanChatRightCell.swift; sourceTree = ""; }; - 7CCB1E4A2F29D87C00472669 /* PostChatDetailUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostChatDetailUseCase.swift; sourceTree = ""; }; - 7CCB1E4D2F29DB3200472669 /* PostChatDetailRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostChatDetailRequest.swift; sourceTree = ""; }; + 7CCB1E4A2F29D87C00472669 /* LostItemPostChatDetailUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemPostChatDetailUseCase.swift; sourceTree = ""; }; + 7CCB1E4D2F29DB3200472669 /* LostItemPostChatDetailRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemPostChatDetailRequest.swift; sourceTree = ""; }; 7CD411E92FEB92280079467B /* HomeTabBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTabBarItem.swift; sourceTree = ""; }; 7CEB3B4A2FBFF4B700AF2B81 /* DeleteDeviceTokenUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteDeviceTokenUseCase.swift; sourceTree = ""; }; 7CED8EC32EF2D1F900457128 /* ShopCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopCollectionView.swift; sourceTree = ""; }; @@ -1435,15 +1431,15 @@ 7CED8EEF2EF2D1F900457128 /* DatePickerDropdownView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerDropdownView.swift; sourceTree = ""; }; 7CED8EF12EF2D1F900457128 /* PostLostItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostLostItemViewModel.swift; sourceTree = ""; }; 7CED8EF22EF2D1F900457128 /* PostLostItemViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostLostItemViewController.swift; sourceTree = ""; }; - 7CED8EF92EF2D1F900457128 /* ChatListTableViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListTableViewModel.swift; sourceTree = ""; }; - 7CED8EFA2EF2D1F900457128 /* ChatListTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListTableViewController.swift; sourceTree = ""; }; - 7CED8EFC2EF2D1F900457128 /* ChatHistoryTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHistoryTableView.swift; sourceTree = ""; }; - 7CED8EFD2EF2D1F900457128 /* ChatTextTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTextTableViewCell.swift; sourceTree = ""; }; - 7CED8EFE2EF2D1F900457128 /* ChatImageTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatImageTableViewCell.swift; sourceTree = ""; }; - 7CED8EFF2EF2D1F900457128 /* ChatDateHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatDateHeaderView.swift; sourceTree = ""; }; - 7CED8F012EF2D1F900457128 /* BlockCheckModalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockCheckModalViewController.swift; sourceTree = ""; }; - 7CED8F022EF2D1F900457128 /* ChatViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatViewModel.swift; sourceTree = ""; }; - 7CED8F032EF2D1F900457128 /* ChatViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatViewController.swift; sourceTree = ""; }; + 7CED8EF92EF2D1F900457128 /* LostItemChatListTableViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemChatListTableViewModel.swift; sourceTree = ""; }; + 7CED8EFA2EF2D1F900457128 /* LostItemChatListTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemChatListTableViewController.swift; sourceTree = ""; }; + 7CED8EFC2EF2D1F900457128 /* LostItemChatHistoryTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemChatHistoryTableView.swift; sourceTree = ""; }; + 7CED8EFD2EF2D1F900457128 /* LostItemChatTextTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemChatTextTableViewCell.swift; sourceTree = ""; }; + 7CED8EFE2EF2D1F900457128 /* LostItemChatImageTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemChatImageTableViewCell.swift; sourceTree = ""; }; + 7CED8EFF2EF2D1F900457128 /* LostItemChatDateHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemChatDateHeaderView.swift; sourceTree = ""; }; + 7CED8F012EF2D1F900457128 /* LostItemBlockCheckModalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemBlockCheckModalViewController.swift; sourceTree = ""; }; + 7CED8F022EF2D1F900457128 /* LostItemChatViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemChatViewModel.swift; sourceTree = ""; }; + 7CED8F032EF2D1F900457128 /* LostItemChatViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemChatViewController.swift; sourceTree = ""; }; 7CED8F072EF2D1F900457128 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; 7CED8F082EF2D1F900457128 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 7CED8F0A2EF2D1F900457128 /* ChangeMyProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeMyProfileViewController.swift; sourceTree = ""; }; @@ -1919,9 +1915,9 @@ D2A8F8BD2C4A78F20090C7A4 /* CoopShopData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoopShopData.swift; sourceTree = ""; }; D2A8F8BF2C4BCD470090C7A4 /* ShareMenuListUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareMenuListUseCase.swift; sourceTree = ""; }; D2A8F8C12C4BD36C0090C7A4 /* ShareService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareService.swift; sourceTree = ""; }; - D2B2193A2D66626900EAF5B1 /* ChatHistoryData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHistoryData.swift; sourceTree = ""; }; + D2B2193A2D66626900EAF5B1 /* LostItemChatHistoryData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemChatHistoryData.swift; sourceTree = ""; }; D2B2193C2D66828200EAF5B1 /* WebSocketManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocketManager.swift; sourceTree = ""; }; - D2B2193E2D66DDF900EAF5B1 /* ChatDateInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatDateInfo.swift; sourceTree = ""; }; + D2B2193E2D66DDF900EAF5B1 /* LostItemChatDateInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemChatDateInfo.swift; sourceTree = ""; }; D2B7EBC92D37C81B00EE46B0 /* PostLostItemRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostLostItemRequest.swift; sourceTree = ""; }; D2B7EBD92D382C8800EE46B0 /* UserTypeResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTypeResponse.swift; sourceTree = ""; }; D2B7EBDB2D382D5400EE46B0 /* CheckAuthUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckAuthUseCase.swift; sourceTree = ""; }; @@ -1943,18 +1939,14 @@ D2D422712BB2A7BE00A8AF04 /* Confirmable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Confirmable.swift; sourceTree = ""; }; D2D422742BB2AB3A00A8AF04 /* PasswordConfirmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordConfirmer.swift; sourceTree = ""; }; D2D422762BB2EA3900A8AF04 /* FindPasswordRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindPasswordRequest.swift; sourceTree = ""; }; - D2D4625F2D63A81500C60864 /* ChatRoomDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRoomDto.swift; sourceTree = ""; }; - D2D462612D63B90100C60864 /* ChatRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRepository.swift; sourceTree = ""; }; - D2D462652D63B98F00C60864 /* FetchChatRoomUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchChatRoomUseCase.swift; sourceTree = ""; }; - D2D462692D63BA5200C60864 /* ChatService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatService.swift; sourceTree = ""; }; - D2D4626B2D63BAC600C60864 /* ChatAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatAPI.swift; sourceTree = ""; }; - D2D4626D2D63BD1400C60864 /* DefaultChatRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultChatRepository.swift; sourceTree = ""; }; - D2D462702D63C5ED00C60864 /* ChatRoomItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRoomItem.swift; sourceTree = ""; }; - D2D462722D63CACC00C60864 /* ChatDetailDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatDetailDto.swift; sourceTree = ""; }; - D2D462742D63CB3E00C60864 /* FetchChatDetailUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchChatDetailUseCase.swift; sourceTree = ""; }; - D2D462792D63D4D400C60864 /* BlockUserUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockUserUseCase.swift; sourceTree = ""; }; - D2D4627B2D6482AA00C60864 /* CreateChatRoomResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateChatRoomResponse.swift; sourceTree = ""; }; - D2D4627D2D64835A00C60864 /* CreateChatRoomUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateChatRoomUseCase.swift; sourceTree = ""; }; + D2D4625F2D63A81500C60864 /* LostItemChatRoomDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemChatRoomDto.swift; sourceTree = ""; }; + D2D462652D63B98F00C60864 /* LostItemFetchChatRoomUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemFetchChatRoomUseCase.swift; sourceTree = ""; }; + D2D462702D63C5ED00C60864 /* LostItemChatRoomItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemChatRoomItem.swift; sourceTree = ""; }; + D2D462722D63CACC00C60864 /* LostItemChatDetailDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemChatDetailDto.swift; sourceTree = ""; }; + D2D462742D63CB3E00C60864 /* LostItemFetchChatDetailUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemFetchChatDetailUseCase.swift; sourceTree = ""; }; + D2D462792D63D4D400C60864 /* LostItemBlockUserUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemBlockUserUseCase.swift; sourceTree = ""; }; + D2D4627B2D6482AA00C60864 /* LostItemCreateChatRoomResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemCreateChatRoomResponse.swift; sourceTree = ""; }; + D2D4627D2D64835A00C60864 /* LostItemCreateChatRoomUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemCreateChatRoomUseCase.swift; sourceTree = ""; }; D2D7C0CF2C28285600C85A85 /* LandRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandRepository.swift; sourceTree = ""; }; D2D7C0D42C28298500C85A85 /* FetchLandListUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchLandListUseCase.swift; sourceTree = ""; }; D2D7C0D62C28299200C85A85 /* FetchLandDetailUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchLandDetailUseCase.swift; sourceTree = ""; }; @@ -2227,6 +2219,7 @@ 7C372ED72F1B7F3100149729 /* LostItem */ = { isa = PBXGroup; children = ( + 7CCB1E4C2F29DB2500472669 /* LostItemChat */, 7C372ED82F1B7F3900149729 /* FetchLostItemListRequest.swift */, 7C8ADD2C2F20B43F00F85BDE /* UpdateLostItemRequest.swift */, D2FFA9842D6333BB00EF8E56 /* ReportLostItemRequest.swift */, @@ -2830,6 +2823,7 @@ 7C8ADD362F20C0A500F85BDE /* LostItem */ = { isa = PBXGroup; children = ( + D2D4625E2D63A78000C60864 /* LostItemChat */, 7C8ADD372F20C0B200F85BDE /* LostItemListDto.swift */, 7C8ADD392F20C10500F85BDE /* LostItemListDataDto.swift */, 7C61D3142F212FB3005FAC3A /* LostItemDataDto.swift */, @@ -2854,6 +2848,11 @@ 7CB11C7F2FB32AF1004E0C80 /* UnsubscribeLostItemKeywordUseCase.swift */, 7C7BD71E302B2B37003C5A15 /* PostLostItemUseCase.swift */, 7C7BD720302B2C25003C5A15 /* ReportLostItemUseCase.swift */, + D2D462652D63B98F00C60864 /* LostItemFetchChatRoomUseCase.swift */, + D2D462742D63CB3E00C60864 /* LostItemFetchChatDetailUseCase.swift */, + D2D462792D63D4D400C60864 /* LostItemBlockUserUseCase.swift */, + D2D4627D2D64835A00C60864 /* LostItemCreateChatRoomUseCase.swift */, + 7CCB1E4A2F29D87C00472669 /* LostItemPostChatDetailUseCase.swift */, ); path = LostItem; sourceTree = ""; @@ -2911,6 +2910,9 @@ 7CA63A2F2F1B13F100226F86 /* LostItemStats.swift */, 7C372F262F1D644A00149729 /* LostItemData.swift */, 7CB11C7B2FB32817004E0C80 /* LostItemKeyword.swift */, + D2D462702D63C5ED00C60864 /* LostItemChatRoomItem.swift */, + D2B2193A2D66626900EAF5B1 /* LostItemChatHistoryData.swift */, + D2B2193E2D66DDF900EAF5B1 /* LostItemChatDateInfo.swift */, ); path = LostItem; sourceTree = ""; @@ -3270,12 +3272,12 @@ path = CallVanChatTableView; sourceTree = ""; }; - 7CCB1E4C2F29DB2500472669 /* Chat */ = { + 7CCB1E4C2F29DB2500472669 /* LostItemChat */ = { isa = PBXGroup; children = ( - 7CCB1E4D2F29DB3200472669 /* PostChatDetailRequest.swift */, + 7CCB1E4D2F29DB3200472669 /* LostItemPostChatDetailRequest.swift */, ); - path = Chat; + path = LostItemChat; sourceTree = ""; }; 7CED8EC52EF2D1F900457128 /* ShopCollectionView */ = { @@ -3354,48 +3356,41 @@ 7CED8EF32EF2D1F900457128 /* PostLostItem */, 7C61D3372F21FADF005FAC3A /* EditLostItem */, 7CB11C162FB1CFED004E0C80 /* LostItemKeyword */, + 7CED8EFB2EF2D1F900457128 /* LostItemChatList */, + 7CED8F042EF2D1F900457128 /* LostItemChat */, ); path = LostItem; sourceTree = ""; }; - 7CED8EFB2EF2D1F900457128 /* ChatList */ = { + 7CED8EFB2EF2D1F900457128 /* LostItemChatList */ = { isa = PBXGroup; children = ( - 7CED8EF92EF2D1F900457128 /* ChatListTableViewModel.swift */, - 7CED8EFA2EF2D1F900457128 /* ChatListTableViewController.swift */, + 7CED8EF92EF2D1F900457128 /* LostItemChatListTableViewModel.swift */, + 7CED8EFA2EF2D1F900457128 /* LostItemChatListTableViewController.swift */, ); - path = ChatList; + path = LostItemChatList; sourceTree = ""; }; - 7CED8F002EF2D1F900457128 /* ChatHistoryTableView */ = { + 7CED8F002EF2D1F900457128 /* LostItemChatHistoryTableView */ = { isa = PBXGroup; children = ( - 7CED8EFC2EF2D1F900457128 /* ChatHistoryTableView.swift */, - 7CED8EFD2EF2D1F900457128 /* ChatTextTableViewCell.swift */, - 7CED8EFE2EF2D1F900457128 /* ChatImageTableViewCell.swift */, - 7CED8EFF2EF2D1F900457128 /* ChatDateHeaderView.swift */, + 7CED8EFC2EF2D1F900457128 /* LostItemChatHistoryTableView.swift */, + 7CED8EFD2EF2D1F900457128 /* LostItemChatTextTableViewCell.swift */, + 7CED8EFE2EF2D1F900457128 /* LostItemChatImageTableViewCell.swift */, + 7CED8EFF2EF2D1F900457128 /* LostItemChatDateHeaderView.swift */, ); - path = ChatHistoryTableView; + path = LostItemChatHistoryTableView; sourceTree = ""; }; - 7CED8F042EF2D1F900457128 /* Chat */ = { + 7CED8F042EF2D1F900457128 /* LostItemChat */ = { isa = PBXGroup; children = ( - 7CED8F002EF2D1F900457128 /* ChatHistoryTableView */, - 7CED8F012EF2D1F900457128 /* BlockCheckModalViewController.swift */, - 7CED8F022EF2D1F900457128 /* ChatViewModel.swift */, - 7CED8F032EF2D1F900457128 /* ChatViewController.swift */, + 7CED8F002EF2D1F900457128 /* LostItemChatHistoryTableView */, + 7CED8F012EF2D1F900457128 /* LostItemBlockCheckModalViewController.swift */, + 7CED8F022EF2D1F900457128 /* LostItemChatViewModel.swift */, + 7CED8F032EF2D1F900457128 /* LostItemChatViewController.swift */, ); - path = Chat; - sourceTree = ""; - }; - 7CED8F052EF2D1F900457128 /* Chat */ = { - isa = PBXGroup; - children = ( - 7CED8EFB2EF2D1F900457128 /* ChatList */, - 7CED8F042EF2D1F900457128 /* Chat */, - ); - path = Chat; + path = LostItemChat; sourceTree = ""; }; 7CED8F092EF2D1F900457128 /* Settings */ = { @@ -4315,7 +4310,6 @@ 7C457B2A2FCE1BA80011D338 /* Home */, 7C016541300A9FF80013DD7B /* Department */, 7CED8EF42EF2D1F900457128 /* LostItem */, - 7CED8F052EF2D1F900457128 /* Chat */, 7CED8F202EF2D1F900457128 /* Setting */, 7CED8F3B2EF2D1F900457128 /* Login */, 7CED8F722EF2D1F900457128 /* Notice */, @@ -4874,7 +4868,6 @@ 7C8A943E2FD424E400DEA6F5 /* Home */, 7CA60D712F66217E007C08C1 /* CallVan */, 7C8ADD362F20C0A500F85BDE /* LostItem */, - D2D4625E2D63A78000C60864 /* Chat */, 833D9C202C6B4F4C00982145 /* NoticeList */, D29F21EE2C4F611F00994554 /* Noti */, D29F21ED2C4F610400994554 /* User */, @@ -5179,7 +5172,6 @@ children = ( 7C640405300F3EA000A9589F /* Department */, 7CA60D6E2F661B29007C08C1 /* CallVan */, - 7CCB1E4C2F29DB2500472669 /* Chat */, 7C372ED72F1B7F3100149729 /* LostItem */, 833D9C3A2C705E3F00982145 /* NoticeList */, 7C04A47E2FB3380700214992 /* Noti */, @@ -5203,36 +5195,14 @@ path = UserInputConfirmer; sourceTree = ""; }; - D2D4625E2D63A78000C60864 /* Chat */ = { + D2D4625E2D63A78000C60864 /* LostItemChat */ = { isa = PBXGroup; children = ( - D2D4625F2D63A81500C60864 /* ChatRoomDto.swift */, - D2D462722D63CACC00C60864 /* ChatDetailDto.swift */, - D2D4627B2D6482AA00C60864 /* CreateChatRoomResponse.swift */, + D2D4625F2D63A81500C60864 /* LostItemChatRoomDto.swift */, + D2D462722D63CACC00C60864 /* LostItemChatDetailDto.swift */, + D2D4627B2D6482AA00C60864 /* LostItemCreateChatRoomResponse.swift */, ); - path = Chat; - sourceTree = ""; - }; - D2D462642D63B97600C60864 /* Chat */ = { - isa = PBXGroup; - children = ( - D2D462652D63B98F00C60864 /* FetchChatRoomUseCase.swift */, - D2D462742D63CB3E00C60864 /* FetchChatDetailUseCase.swift */, - D2D462792D63D4D400C60864 /* BlockUserUseCase.swift */, - D2D4627D2D64835A00C60864 /* CreateChatRoomUseCase.swift */, - 7CCB1E4A2F29D87C00472669 /* PostChatDetailUseCase.swift */, - ); - path = Chat; - sourceTree = ""; - }; - D2D4626F2D63C58900C60864 /* Chat */ = { - isa = PBXGroup; - children = ( - D2D462702D63C5ED00C60864 /* ChatRoomItem.swift */, - D2B2193A2D66626900EAF5B1 /* ChatHistoryData.swift */, - D2B2193E2D66DDF900EAF5B1 /* ChatDateInfo.swift */, - ); - path = Chat; + path = LostItemChat; sourceTree = ""; }; D2D7C0D12C28293200C85A85 /* Land */ = { @@ -5271,7 +5241,6 @@ 7C8A00372FD400000011D338 /* Home */, 7CC42B452F5993DF00940CE1 /* CallVan */, 7C8ADD412F20C3CE00F85BDE /* LostItem */, - D2D462642D63B97600C60864 /* Chat */, 833D9C3F2C705F0E00982145 /* NoticeList */, D208A7C72CABB467007040E7 /* Core */, D20AB9502C5534DD006BC684 /* Noti */, @@ -5291,11 +5260,10 @@ D2FB23492BFD451F0098BA2B /* Model */ = { isa = PBXGroup; children = ( - 7C8A00362FD400000011D338 /* Home */, 7C7CCE6E2F866E8900E3A54B /* Core */, + 7C8A00362FD400000011D338 /* Home */, 7CA63A1F2F1AF73500226F86 /* LostItem */, D815DD972E08216B004A72BE /* Order */, - D2D4626F2D63C58900C60864 /* Chat */, D215726D2CEE28C30061E725 /* Timetable */, 833D9C102C6B269000982145 /* NoticeList */, D29F21EF2C4FD22900994554 /* Land */, @@ -5321,7 +5289,6 @@ D2D7C0CF2C28285600C85A85 /* LandRepository.swift */, D20AB94D2C553427006BC684 /* NotiRepository.swift */, 83DD0D7C2C4F91AD00278ABD /* BusRepository.swift */, - D2D462612D63B90100C60864 /* ChatRepository.swift */, 833D9C382C7058C200982145 /* NoticeListRepository.swift */, D208A79F2CA10F1A007040E7 /* AbTestRepository.swift */, D208A7C52CABB426007040E7 /* CoreRepository.swift */, @@ -5348,7 +5315,6 @@ 833D9C482C7068CB00982145 /* DefaultNoticeListRepository.swift */, D208A7D22CAD7436007040E7 /* DefaultAbTestRepository.swift */, D208A7CE2CABB85B007040E7 /* DefaultCoreRepository.swift */, - D2D4626D2D63BD1400C60864 /* DefaultChatRepository.swift */, 7CA60D642F6619B7007C08C1 /* DefaultCallVanRepository.swift */, 7CA686792FFCB758007937E9 /* DefaultNotificationHistoryRepository.swift */, 7C640401300F39BB00A9589F /* DefaultDepartmentRepository.swift */, @@ -5411,7 +5377,6 @@ 833D9C462C7067AE00982145 /* NoticeListAPI.swift */, D208A7AD2CA117AA007040E7 /* AbTestAPI.swift */, D208A7CC2CABB7A3007040E7 /* CoreAPI.swift */, - D2D4626B2D63BAC600C60864 /* ChatAPI.swift */, 7C8ADD342F20BF4600F85BDE /* LostItemAPI.swift */, 7CA60D6C2F661A6D007C08C1 /* CallVanAPI.swift */, 7C8A94432FD4279B00DEA6F5 /* HomeAPI.swift */, @@ -5432,7 +5397,6 @@ D2FB23722C04305A0098BA2B /* Service */ = { isa = PBXGroup; children = ( - D2D462692D63BA5200C60864 /* ChatService.swift */, D20AB9572C553621006BC684 /* NotiService.swift */, D2FB236D2C0410510098BA2B /* LogAnalyticsService.swift */, D29F21C22C4DE27C00994554 /* UserService.swift */, @@ -5706,7 +5670,7 @@ D22C54582C68B033000826DA /* ReportReviewRequest.swift in Sources */, D20AD6F92E02A11D0067760A /* FindIdSmsResponse.swift in Sources */, D27DD0D22BA608310081FD36 /* AppDelegate.swift in Sources */, - D2D4627C2D6482AA00C60864 /* CreateChatRoomResponse.swift in Sources */, + D2D4627C2D6482AA00C60864 /* LostItemCreateChatRoomResponse.swift in Sources */, D29F21D12C4E164C00994554 /* DefaultTimetableRepository.swift in Sources */, D22C54562C68B033000826DA /* WriteReviewRequest.swift in Sources */, 7C17EEA82EBF4F25008BCA89 /* ShopSearch.swift in Sources */, @@ -5723,7 +5687,7 @@ D29F21F62C4FD2C700994554 /* FetchLandDetailRequest.swift in Sources */, D208A7A02CA10F1A007040E7 /* AbTestRepository.swift in Sources */, D8F5C54A2E6EF76800FB6708 /* OrderFloatingButton.swift in Sources */, - D2B2193B2D66626900EAF5B1 /* ChatHistoryData.swift in Sources */, + D2B2193B2D66626900EAF5B1 /* LostItemChatHistoryData.swift in Sources */, 83DD0DBB2C551E5100278ABD /* BusTimetableInfo.swift in Sources */, B47839052E70FAFE00D002E3 /* OrderShopSummary.swift in Sources */, D249CD3D2C159350000813F9 /* DateProvider.swift in Sources */, @@ -5783,12 +5747,12 @@ 7C8ADD352F20BF4600F85BDE /* LostItemAPI.swift in Sources */, 7C8A94422FD4278800DEA6F5 /* HomeService.swift in Sources */, 83DD0D762C4F6FDE00278ABD /* BusPlace.swift in Sources */, - D2B2193F2D66DDF900EAF5B1 /* ChatDateInfo.swift in Sources */, + D2B2193F2D66DDF900EAF5B1 /* LostItemChatDateInfo.swift in Sources */, D89F8FD92DF0A8A4000265DC /* DefaultTextField.swift in Sources */, 7CC42B392F58284100940CE1 /* CallVanNotificationTableView.swift in Sources */, 7CC42B332F58247D00940CE1 /* CallVanNotification.swift in Sources */, B46B8CC22E76CB7300A8E797 /* ShopSummaryDto.swift in Sources */, - D2D462662D63B98F00C60864 /* FetchChatRoomUseCase.swift in Sources */, + D2D462662D63B98F00C60864 /* LostItemFetchChatRoomUseCase.swift in Sources */, 7CA0D6232EC74ADE00E9A282 /* ZoomedImageCollectionViewCell.swift in Sources */, D2BD36962BAA027A00FAE609 /* TokenRefreshRequest.swift in Sources */, D2D7C0D72C28299200C85A85 /* FetchLandDetailUseCase.swift in Sources */, @@ -5905,7 +5869,7 @@ D2FFA9702D5F4DC500EF8E56 /* CheckLoginUseCase.swift in Sources */, 833D9C452C70672C00982145 /* NoticeListService.swift in Sources */, 7CA60D652F6619B7007C08C1 /* DefaultCallVanRepository.swift in Sources */, - D2D462602D63A81500C60864 /* ChatRoomDto.swift in Sources */, + D2D462602D63A81500C60864 /* LostItemChatRoomDto.swift in Sources */, D20AD6ED2E028FA20067760A /* FindIdEmailRequest.swift in Sources */, 7C5583622F11779400D3A980 /* ShopInfoFooterView.swift in Sources */, 831ABDC62CA2A7C20099B70C /* FetchRecentSearchedWordUseCase.swift in Sources */, @@ -5987,11 +5951,11 @@ 7CED90C62EF2D1F900457128 /* TimetableCollectionViewCell.swift in Sources */, 7C016556300AA2E10013DD7B /* DepartmentCategory.swift in Sources */, 7CED90CA2EF2D1F900457128 /* LandOptionCollectionViewCell.swift in Sources */, - 7CED90CB2EF2D1F900457128 /* ChatListTableViewModel.swift in Sources */, + 7CED90CB2EF2D1F900457128 /* LostItemChatListTableViewModel.swift in Sources */, 7CED90CE2EF2D1F900457128 /* ForceModifyUserViewController.swift in Sources */, 7CED90CF2EF2D1F900457128 /* ChangeMyProfileViewModel.swift in Sources */, 7C0C18912F67FA58001781B3 /* ReportCallVanUserUseCase.swift in Sources */, - 7CED90D02EF2D1F900457128 /* ChatHistoryTableView.swift in Sources */, + 7CED90D02EF2D1F900457128 /* LostItemChatHistoryTableView.swift in Sources */, 7CF77B543003B276003642C1 /* HomeLogoView.swift in Sources */, 7C016558300AA3790013DD7B /* DepartmentTask.swift in Sources */, 7CED90D12EF2D1F900457128 /* NoticeSearchViewController.swift in Sources */, @@ -6002,11 +5966,11 @@ 7CED90D72EF2D1F900457128 /* RecommendedKeywordCollectionView.swift in Sources */, 7CED90D92EF2D1F900457128 /* DiningViewController.swift in Sources */, 7C7BD725302B3E25003C5A15 /* NoticeAISummaryView.swift in Sources */, - 7CCB1E4B2F29D87C00472669 /* PostChatDetailUseCase.swift in Sources */, + 7CCB1E4B2F29D87C00472669 /* LostItemPostChatDetailUseCase.swift in Sources */, 7CED90DA2EF2D1F900457128 /* RecentSearchTableView.swift in Sources */, 7C7BD727302D8002003C5A15 /* NoticeAISummaryDto.swift in Sources */, 7C61D35F2F24000C005FAC3A /* LostItemDataButtonsView.swift in Sources */, - 7CED90DB2EF2D1F900457128 /* ChatImageTableViewCell.swift in Sources */, + 7CED90DB2EF2D1F900457128 /* LostItemChatImageTableViewCell.swift in Sources */, 7CED90DC2EF2D1F900457128 /* ShopSummaryDeliveryButton.swift in Sources */, 7CED90DD2EF2D1F900457128 /* AddDirectCollectionViewCell.swift in Sources */, 7CED90DF2EF2D1F900457128 /* LandDetailViewModel.swift in Sources */, @@ -6023,9 +5987,9 @@ 7CED90F12EF2D1F900457128 /* ForceUpdateViewModel.swift in Sources */, 7CA60D6D2F661A6D007C08C1 /* CallVanAPI.swift in Sources */, 7CED90F22EF2D1F900457128 /* ShopSearchViewModel.swift in Sources */, - 7CED90F42EF2D1F900457128 /* ChatListTableViewController.swift in Sources */, + 7CED90F42EF2D1F900457128 /* LostItemChatListTableViewController.swift in Sources */, 7CED90F52EF2D1F900457128 /* DiningViewModel.swift in Sources */, - 7CED90F62EF2D1F900457128 /* ChatDateHeaderView.swift in Sources */, + 7CED90F62EF2D1F900457128 /* LostItemChatDateHeaderView.swift in Sources */, 7CED90F72EF2D1F900457128 /* ReviewImageCollectionViewCell.swift in Sources */, 7CED90FA2EF2D1F900457128 /* LandDetailViewController.swift in Sources */, 7CED90FB2EF2D1F900457128 /* TabBarCollectionViewCell.swift in Sources */, @@ -6042,10 +6006,10 @@ 7CB11C802FB32AF1004E0C80 /* UnsubscribeLostItemKeywordUseCase.swift in Sources */, 7CED91022EF2D1F900457128 /* ShopCollectionViewCell.swift in Sources */, 7CED91042EF2D1F900457128 /* TimetableCell.swift in Sources */, - 7CCB1E4E2F29DB3200472669 /* PostChatDetailRequest.swift in Sources */, + 7CCB1E4E2F29DB3200472669 /* LostItemPostChatDetailRequest.swift in Sources */, 7CED91052EF2D1F900457128 /* SelectTypeFormViewController.swift in Sources */, 7CED91072EF2D1F900457128 /* AddDirectHeaderView.swift in Sources */, - 7CED91082EF2D1F900457128 /* BlockCheckModalViewController.swift in Sources */, + 7CED91082EF2D1F900457128 /* LostItemBlockCheckModalViewController.swift in Sources */, 7CED91092EF2D1F900457128 /* FacilityInfoViewController.swift in Sources */, 7CED910A2EF2D1F900457128 /* LeftAlignedFlowLayout.swift in Sources */, 7CED910B2EF2D1F900457128 /* AddClassCollectionView.swift in Sources */, @@ -6055,7 +6019,7 @@ 7CED910F2EF2D1F900457128 /* ShopCollectionView.swift in Sources */, 7CED91122EF2D1F900457128 /* LectureView.swift in Sources */, 7CA60D7D2F665128007C08C1 /* DeleteNotificationUseCase.swift in Sources */, - 7CED91142EF2D1F900457128 /* ChatViewController.swift in Sources */, + 7CED91142EF2D1F900457128 /* LostItemChatViewController.swift in Sources */, 7CED91152EF2D1F900457128 /* LoginViewModel.swift in Sources */, 7C7F56B42FFB6A6400847151 /* NotificationPopUpViewController.swift in Sources */, 7CED91162EF2D1F900457128 /* ShopSortOptionSheetViewController.swift in Sources */, @@ -6326,14 +6290,14 @@ 7CED91BB2EF2D1F900457128 /* EnterFormViewController.swift in Sources */, 7CF77B4130027314003642C1 /* ProfileUserInfoView.swift in Sources */, 7CED91BD2EF2D1F900457128 /* RecommendedSearchCollectionViewCell.swift in Sources */, - 7CED91C02EF2D1F900457128 /* ChatTextTableViewCell.swift in Sources */, + 7CED91C02EF2D1F900457128 /* LostItemChatTextTableViewCell.swift in Sources */, 7CED91C12EF2D1F900457128 /* ShopViewModel.swift in Sources */, 7CED91C22EF2D1F900457128 /* ReviewImageCollectionView.swift in Sources */, 7CED91C62EF2D1F900457128 /* HotArticlesNoticeTableViewCell.swift in Sources */, 7C016576300BD9610013DD7B /* FetchDepartmentByCategoryUseCase.swift in Sources */, 7CED91C92EF2D1F900457128 /* CategoryCollectionView.swift in Sources */, 7CED91CB2EF2D1F900457128 /* ShopSummaryImagesCollectionViewCell.swift in Sources */, - 7CED91CD2EF2D1F900457128 /* ChatViewModel.swift in Sources */, + 7CED91CD2EF2D1F900457128 /* LostItemChatViewModel.swift in Sources */, 7C640400300F395300A9589F /* DepartmentRepository.swift in Sources */, 7CED91CE2EF2D1F900457128 /* NotiViewModel.swift in Sources */, 7CED91CF2EF2D1F900457128 /* ManageNoticeKeywordViewModel.swift in Sources */, @@ -6409,7 +6373,7 @@ D20AB9582C553621006BC684 /* NotiService.swift in Sources */, 7CF77B6530056EB9003642C1 /* NoticeKeywordSearchButton.swift in Sources */, D29F21DF2C4F2E3000994554 /* FetchShopDataRequest.swift in Sources */, - D2D4627A2D63D4D400C60864 /* BlockUserUseCase.swift in Sources */, + D2D4627A2D63D4D400C60864 /* LostItemBlockUserUseCase.swift in Sources */, D21572562CEDC1830061E725 /* ModifyFrameUseCase.swift in Sources */, D2FB23572BFD50120098BA2B /* DefaultShopRepository.swift in Sources */, 833D9C602C7457EE00982145 /* NoticeDataInfo.swift in Sources */, @@ -6418,7 +6382,7 @@ D2FB23602BFDABBB0098BA2B /* Router.swift in Sources */, D89F8FDB2DF12847000265DC /* StatefulButton.swift in Sources */, D208A7BB2CA169DF007040E7 /* FetchBeneficialShopUseCase.swift in Sources */, - D2D462712D63C5ED00C60864 /* ChatRoomItem.swift in Sources */, + D2D462712D63C5ED00C60864 /* LostItemChatRoomItem.swift in Sources */, 7C8ADD2D2F20B43F00F85BDE /* UpdateLostItemRequest.swift in Sources */, 7C7BD723302B3D8F003C5A15 /* NoticeAISummary.swift in Sources */, D2B7EBCA2D37C81B00EE46B0 /* PostLostItemRequest.swift in Sources */, @@ -6513,7 +6477,7 @@ D2DA83912C181A1400A2F156 /* FetchShopDataUseCase.swift in Sources */, 7CD411EA2FEB92280079467B /* HomeTabBarItem.swift in Sources */, D208A7AC2CA112D0007040E7 /* AssignAbTestResponse.swift in Sources */, - D2D462732D63CACC00C60864 /* ChatDetailDto.swift in Sources */, + D2D462732D63CACC00C60864 /* LostItemChatDetailDto.swift in Sources */, 7C333EA330303678009D2B89 /* ModalContentView.swift in Sources */, D20AD6FF2E02A4AB0067760A /* FindIdEmailUseCase.swift in Sources */, D215724E2CEDBEC60061E725 /* FrameDto.swift in Sources */, @@ -6529,8 +6493,7 @@ 8380FDC32BC1CBB800120036 /* BusCourseDto.swift in Sources */, 83DD0D742C4F6FD000278ABD /* BusType.swift in Sources */, 7C372F242F1CDB4400149729 /* LostItemDataContentView.swift in Sources */, - D2D4626E2D63BD1400C60864 /* DefaultChatRepository.swift in Sources */, - D2D462752D63CB3E00C60864 /* FetchChatDetailUseCase.swift in Sources */, + D2D462752D63CB3E00C60864 /* LostItemFetchChatDetailUseCase.swift in Sources */, 83DD0DBF2C55258200278ABD /* FetchShuttleBusRoutesUseCase.swift in Sources */, D2078BF62BC4CCEB00A39861 /* EventDto.swift in Sources */, D20AD6F32E029F7F0067760A /* SendVerificationEmailUseCase.swift in Sources */, @@ -6557,7 +6520,6 @@ 7C17EEB22EBF7175008BCA89 /* FetchSearchShopUseCase.swift in Sources */, D2B2193D2D66828300EAF5B1 /* WebSocketManager.swift in Sources */, 7C7CCE702F866E9400E3A54B /* AppPath.swift in Sources */, - D2D4626A2D63BA5200C60864 /* ChatService.swift in Sources */, 7C61D3472F220EA2005FAC3A /* EditLostItemCategoryView.swift in Sources */, 7CED92462EF3DC2800457128 /* OrderCategoryCollectionViewCell.swift in Sources */, 7C8A94402FD426B400DEA6F5 /* HomeRepository.swift in Sources */, @@ -6592,8 +6554,7 @@ D85939752DC12C9D00CE4CB2 /* CheckDuplicatedIdUsecase.swift in Sources */, 7CF77B3C300254C8003642C1 /* ProfileView.swift in Sources */, 83BB83132CEC814800946623 /* FetchKeywordNoticePhraseUseCase.swift in Sources */, - D2D4627E2D64835B00C60864 /* CreateChatRoomUseCase.swift in Sources */, - D2D4626C2D63BAC600C60864 /* ChatAPI.swift in Sources */, + D2D4627E2D64835B00C60864 /* LostItemCreateChatRoomUseCase.swift in Sources */, D21572502CEDC1680061E725 /* CreateFrameUseCase.swift in Sources */, D2009F762C17270F00211D1B /* CalendarDate.swift in Sources */, D29F21CC2C4E157E00994554 /* FetchDeptListUseCase.swift in Sources */, @@ -6602,7 +6563,6 @@ D21572662CEDDDC60061E725 /* FetchMySemesterUseCase.swift in Sources */, B68D2CEB2EA50C1E00F3B479 /* UIViewController+Toast.swift in Sources */, D249CD2A2C12DD34000813F9 /* DiningAPI.swift in Sources */, - D2D462622D63B90100C60864 /* ChatRepository.swift in Sources */, 83E770FC2D05B6DD00EB3F84 /* FetchEmergencyNoticeUseCase.swift in Sources */, 833D9C122C6B26A900982145 /* NoticeListType.swift in Sources */, D2FB23452BFB6D090098BA2B /* CustomNavigationController.swift in Sources */, From e131e85f9bbd6c84f038435545dccd9a11bca03c Mon Sep 17 00:00:00 2001 From: GI JEONG HONG Date: Wed, 19 Aug 2026 23:17:00 +0900 Subject: [PATCH 2/5] =?UTF-8?q?chore:=20NotificationItem=EC=9D=84=20Notifi?= =?UTF-8?q?cationHistoryItem=EC=9C=BC=EB=A1=9C=20=EB=A6=AC=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ....swift => NotificationHistoryRecord.swift} | 4 ++-- .../NotificationHistoryService.swift | 22 +++++++++---------- ...DefaultNotificationHistoryRepository.swift | 4 ++-- ...em.swift => NotificationHistoryItem.swift} | 19 ++++++++-------- .../NotificationHistoryRepository.swift | 2 +- .../Home/FetchNotificationListUseCase.swift | 4 ++-- .../Notification/NotificationViewModel.swift | 2 +- NotificationService/NotificationService.swift | 2 +- koin.xcodeproj/project.pbxproj | 20 ++++++++--------- 9 files changed, 39 insertions(+), 40 deletions(-) rename Common/NotificationHistory/{NotificationRecord.swift => NotificationHistoryRecord.swift} (89%) rename Koin/Domain/Model/Home/{NotificationItem.swift => NotificationHistoryItem.swift} (84%) diff --git a/Common/NotificationHistory/NotificationRecord.swift b/Common/NotificationHistory/NotificationHistoryRecord.swift similarity index 89% rename from Common/NotificationHistory/NotificationRecord.swift rename to Common/NotificationHistory/NotificationHistoryRecord.swift index 99974e1ee..4a956787d 100644 --- a/Common/NotificationHistory/NotificationRecord.swift +++ b/Common/NotificationHistory/NotificationHistoryRecord.swift @@ -1,5 +1,5 @@ // -// NotificationRecord.swift +// NotificationHistoryRecord.swift // koin // // Created by 홍기정 on 7/6/26. @@ -9,7 +9,7 @@ import SwiftData import Foundation @Model -final class NotificationRecord { +final class NotificationHistoryRecord { var body: String var title: String var category: AppPath diff --git a/Common/NotificationHistory/NotificationHistoryService.swift b/Common/NotificationHistory/NotificationHistoryService.swift index 23304b2c1..1ad0d49b2 100644 --- a/Common/NotificationHistory/NotificationHistoryService.swift +++ b/Common/NotificationHistory/NotificationHistoryService.swift @@ -9,8 +9,8 @@ import SwiftData import Foundation protocol NotificationHistoryService { - func insert(record: NotificationRecord) async throws - func fetchAll() async throws -> [NotificationRecord] + func insert(record: NotificationHistoryRecord) async throws + func fetchAll() async throws -> [NotificationHistoryRecord] func markAsRead(messageId: String) async throws func markAllAsRead() async throws func delete(messageId: String) async throws @@ -25,13 +25,13 @@ final class DefaultNotificationHistoryService: NotificationHistoryService { // MARK: - Initializer init() { container = try? ModelContainer( - for: NotificationRecord.self, + for: NotificationHistoryRecord.self, configurations: .init(groupContainer: .identifier("group.com.bcsdlab.koin")) ) } // MARK: - Create - func insert(record: NotificationRecord) async throws { + func insert(record: NotificationHistoryRecord) async throws { guard let container else { throw SwiftDataError.loadIssueModelContainer } @@ -48,7 +48,7 @@ final class DefaultNotificationHistoryService: NotificationHistoryService { } // MARK: - Read - func fetchAll() async throws -> [NotificationRecord] { + func fetchAll() async throws -> [NotificationHistoryRecord] { guard let container else { throw SwiftDataError.loadIssueModelContainer } @@ -60,7 +60,7 @@ final class DefaultNotificationHistoryService: NotificationHistoryService { } return try await MainActor.run { - var descriptor = FetchDescriptor( + var descriptor = FetchDescriptor( sortBy: [SortDescriptor(\.createdAt, order: .reverse)] ) descriptor.fetchLimit = .max @@ -76,7 +76,7 @@ final class DefaultNotificationHistoryService: NotificationHistoryService { } try await MainActor.run { - var descriptor = FetchDescriptor( + var descriptor = FetchDescriptor( predicate: #Predicate { notification in notification.messageId == messageId } @@ -97,7 +97,7 @@ final class DefaultNotificationHistoryService: NotificationHistoryService { } try await MainActor.run { - var descriptor = FetchDescriptor() + var descriptor = FetchDescriptor() descriptor.fetchLimit = .max try container.mainContext.enumerate(descriptor) { notification in notification.isRead = true @@ -115,7 +115,7 @@ final class DefaultNotificationHistoryService: NotificationHistoryService { } try await MainActor.run { - try container.mainContext.delete(model: NotificationRecord.self, where: #Predicate { notification in + try container.mainContext.delete(model: NotificationHistoryRecord.self, where: #Predicate { notification in notification.messageId == messageId }) try container.mainContext.save() @@ -130,7 +130,7 @@ final class DefaultNotificationHistoryService: NotificationHistoryService { } try await MainActor.run { - try container.mainContext.delete(model: NotificationRecord.self) + try container.mainContext.delete(model: NotificationHistoryRecord.self) try container.mainContext.save() } @@ -149,7 +149,7 @@ extension DefaultNotificationHistoryService { } try await MainActor.run { - try container.mainContext.delete(model: NotificationRecord.self, where: #Predicate { notification in + try container.mainContext.delete(model: NotificationHistoryRecord.self, where: #Predicate { notification in notification.createdAt < expirationDate }) try container.mainContext.save() diff --git a/Koin/Data/Repository/DefaultNotificationHistoryRepository.swift b/Koin/Data/Repository/DefaultNotificationHistoryRepository.swift index 49acccc8e..889c0dcf7 100644 --- a/Koin/Data/Repository/DefaultNotificationHistoryRepository.swift +++ b/Koin/Data/Repository/DefaultNotificationHistoryRepository.swift @@ -15,10 +15,10 @@ final class DefaultNotificationHistoryRepository: NotificationHistoryRepository self.service = service } - func fetchAll() async throws -> [NotificationItem] { + func fetchAll() async throws -> [NotificationHistoryItem] { try await service.fetchAll() .compactMap { - NotificationItem.init(from: $0) + NotificationHistoryItem.init(from: $0) } } diff --git a/Koin/Domain/Model/Home/NotificationItem.swift b/Koin/Domain/Model/Home/NotificationHistoryItem.swift similarity index 84% rename from Koin/Domain/Model/Home/NotificationItem.swift rename to Koin/Domain/Model/Home/NotificationHistoryItem.swift index 5cd986f1c..f2c324eb7 100644 --- a/Koin/Domain/Model/Home/NotificationItem.swift +++ b/Koin/Domain/Model/Home/NotificationHistoryItem.swift @@ -1,5 +1,5 @@ // -// NotificationItem.swift +// NotificationHistoryItem.swift // koin // // Created by 홍기정 on 6/3/26. @@ -7,7 +7,7 @@ import Foundation -struct NotificationItem { +struct NotificationHistoryItem { let id: String var isRead: Bool let icon: ImageAsset @@ -18,10 +18,9 @@ struct NotificationItem { let dateText: String } -extension NotificationItem { - - init?(from record: NotificationRecord) { - guard let icon = NotificationItem.icon(for: record.category) else { +extension NotificationHistoryItem { + init?(from record: NotificationHistoryRecord) { + guard let icon = NotificationHistoryItem.icon(for: record.category) else { return nil } self.id = record.messageId @@ -31,9 +30,9 @@ extension NotificationItem { self.uri = record.schemeUri self.title = record.title self.content = record.body - self.dateText = NotificationItem.dateText(for: record.createdAt) + self.dateText = NotificationHistoryItem.dateText(for: record.createdAt) } - + static func icon(for appPath: AppPath) -> ImageAsset? { switch appPath { case .shop: @@ -52,7 +51,7 @@ extension NotificationItem { return nil } } - + static func dateText(for createdAt: Date) -> String { let now = Date() let compareComponents = Calendar.current.dateComponents( @@ -60,7 +59,7 @@ extension NotificationItem { from: createdAt, to: now ) - + if let day = compareComponents.day, 1 <= day { return "\(day)일 전" } diff --git a/Koin/Domain/Repository/NotificationHistoryRepository.swift b/Koin/Domain/Repository/NotificationHistoryRepository.swift index 4ef807fa6..2937bc1c6 100644 --- a/Koin/Domain/Repository/NotificationHistoryRepository.swift +++ b/Koin/Domain/Repository/NotificationHistoryRepository.swift @@ -8,7 +8,7 @@ import Combine protocol NotificationHistoryRepository { - func fetchAll() async throws -> [NotificationItem] + func fetchAll() async throws -> [NotificationHistoryItem] func deleteAll() async throws func delete(id: String) async throws func markAsRead(id: String) async throws diff --git a/Koin/Domain/UseCase/Home/FetchNotificationListUseCase.swift b/Koin/Domain/UseCase/Home/FetchNotificationListUseCase.swift index 250edd349..c2dddbdc3 100644 --- a/Koin/Domain/UseCase/Home/FetchNotificationListUseCase.swift +++ b/Koin/Domain/UseCase/Home/FetchNotificationListUseCase.swift @@ -9,7 +9,7 @@ import Combine import Foundation protocol FetchNotificationHistoryUseCase { - func execute() async throws -> [NotificationItem] + func execute() async throws -> [NotificationHistoryItem] } final class DefaultFetchNotificationHistoryUseCase: FetchNotificationHistoryUseCase { @@ -20,7 +20,7 @@ final class DefaultFetchNotificationHistoryUseCase: FetchNotificationHistoryUseC self.notificationHistoryRepository = notificationHistoryRepository } - func execute() async throws -> [NotificationItem] { + func execute() async throws -> [NotificationHistoryItem] { try await notificationHistoryRepository.fetchAll() } } diff --git a/Koin/Presentation/Home/Notification/NotificationViewModel.swift b/Koin/Presentation/Home/Notification/NotificationViewModel.swift index 6c0f6d929..e25518090 100644 --- a/Koin/Presentation/Home/Notification/NotificationViewModel.swift +++ b/Koin/Presentation/Home/Notification/NotificationViewModel.swift @@ -21,7 +21,7 @@ final class NotificationViewModel: ViewModelProtocol { } enum Output { - case updateNotifications([NotificationItem]) + case updateNotifications([NotificationHistoryItem]) case showToast(String) } diff --git a/NotificationService/NotificationService.swift b/NotificationService/NotificationService.swift index 9512a76ff..7ffa92300 100644 --- a/NotificationService/NotificationService.swift +++ b/NotificationService/NotificationService.swift @@ -70,7 +70,7 @@ extension NotificationService { throw NotificationHistoryError.parsingError } - let notificationRecord = NotificationRecord( + let notificationRecord = NotificationHistoryRecord( body: body, title: title, category: appPath, diff --git a/koin.xcodeproj/project.pbxproj b/koin.xcodeproj/project.pbxproj index b42a99600..14f123bcd 100644 --- a/koin.xcodeproj/project.pbxproj +++ b/koin.xcodeproj/project.pbxproj @@ -237,8 +237,8 @@ 7C7F43282F65F31500CC5860 /* CallVanReportRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7F43272F65F31500CC5860 /* CallVanReportRequest.swift */; }; 7C7F432A2F65F42300CC5860 /* CallVanRecruitmentState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7F43292F65F42300CC5860 /* CallVanRecruitmentState.swift */; }; 7C7F56B42FFB6A6400847151 /* NotificationPopUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7F56B32FFB6A6400847151 /* NotificationPopUpViewController.swift */; }; - 7C7F56B72FFB9FE000847151 /* NotificationRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7F56B62FFB9FE000847151 /* NotificationRecord.swift */; }; - 7C7F56B82FFBA06200847151 /* NotificationRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7F56B62FFB9FE000847151 /* NotificationRecord.swift */; }; + 7C7F56B72FFB9FE000847151 /* NotificationHistoryRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7F56B62FFB9FE000847151 /* NotificationHistoryRecord.swift */; }; + 7C7F56B82FFBA06200847151 /* NotificationHistoryRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7F56B62FFB9FE000847151 /* NotificationHistoryRecord.swift */; }; 7C7F56B92FFBA0A700847151 /* AppPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7CCE6F2F866E9400E3A54B /* AppPath.swift */; }; 7C7F56BB2FFBA18100847151 /* NotificationHistoryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7F56BA2FFBA18100847151 /* NotificationHistoryService.swift */; }; 7C7F56BC2FFBA18100847151 /* NotificationHistoryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C7F56BA2FFBA18100847151 /* NotificationHistoryService.swift */; }; @@ -282,7 +282,7 @@ 7C8BFCD62FD032A000963679 /* View+linespacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8BFCD52FD032A000963679 /* View+linespacing.swift */; }; 7C8BFCE12FD04B8000963679 /* HomeDiningItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8BFCDD2FD04B8000963679 /* HomeDiningItem.swift */; }; 7C8BFCE32FD04B8000963679 /* HomeHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8BFCDE2FD04B8000963679 /* HomeHeader.swift */; }; - 7C8BFCE42FD04B8000963679 /* NotificationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8BFCE02FD04B8000963679 /* NotificationItem.swift */; }; + 7C8BFCE42FD04B8000963679 /* NotificationHistoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8BFCE02FD04B8000963679 /* NotificationHistoryItem.swift */; }; 7C8BFCE52FD04B8000963679 /* CategoryModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8BFCDC2FD04B8000963679 /* CategoryModels.swift */; }; 7C8BFCEB2FD04BA000963679 /* FetchHomeDiningListUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8BFCE62FD04BA000963679 /* FetchHomeDiningListUseCase.swift */; }; 7C8BFCEC2FD04BA000963679 /* FetchHeaderUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8BFCE72FD04BA000963679 /* FetchHeaderUseCase.swift */; }; @@ -1258,7 +1258,7 @@ 7C7F43272F65F31500CC5860 /* CallVanReportRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVanReportRequest.swift; sourceTree = ""; }; 7C7F43292F65F42300CC5860 /* CallVanRecruitmentState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVanRecruitmentState.swift; sourceTree = ""; }; 7C7F56B32FFB6A6400847151 /* NotificationPopUpViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPopUpViewController.swift; sourceTree = ""; }; - 7C7F56B62FFB9FE000847151 /* NotificationRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRecord.swift; sourceTree = ""; }; + 7C7F56B62FFB9FE000847151 /* NotificationHistoryRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHistoryRecord.swift; sourceTree = ""; }; 7C7F56BA2FFBA18100847151 /* NotificationHistoryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHistoryService.swift; sourceTree = ""; }; 7C82FF772E9D18C7006335A7 /* OrderShopDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderShopDetail.swift; sourceTree = ""; }; 7C85D2742EBDE374005E63FF /* FetchOrderShopMenusAndGroupsFromShopUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchOrderShopMenusAndGroupsFromShopUseCase.swift; sourceTree = ""; }; @@ -1301,7 +1301,7 @@ 7C8BFCDC2FD04B8000963679 /* CategoryModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryModels.swift; sourceTree = ""; }; 7C8BFCDD2FD04B8000963679 /* HomeDiningItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeDiningItem.swift; sourceTree = ""; }; 7C8BFCDE2FD04B8000963679 /* HomeHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeHeader.swift; sourceTree = ""; }; - 7C8BFCE02FD04B8000963679 /* NotificationItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItem.swift; sourceTree = ""; }; + 7C8BFCE02FD04B8000963679 /* NotificationHistoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHistoryItem.swift; sourceTree = ""; }; 7C8BFCE62FD04BA000963679 /* FetchHomeDiningListUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchHomeDiningListUseCase.swift; sourceTree = ""; }; 7C8BFCE72FD04BA000963679 /* FetchHeaderUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchHeaderUseCase.swift; sourceTree = ""; }; 7C8BFCE82FD04BA000963679 /* FetchHomeCountsUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchHomeCountsUseCase.swift; sourceTree = ""; }; @@ -2773,7 +2773,7 @@ 7C8BFCDD2FD04B8000963679 /* HomeDiningItem.swift */, 7C8BFCDE2FD04B8000963679 /* HomeHeader.swift */, 7C8BFCF02FD04B800963679 /* HomeCounts.swift */, - 7C8BFCE02FD04B8000963679 /* NotificationItem.swift */, + 7C8BFCE02FD04B8000963679 /* NotificationHistoryItem.swift */, ); path = Home; sourceTree = ""; @@ -2952,7 +2952,7 @@ isa = PBXGroup; children = ( 7C7F56BA2FFBA18100847151 /* NotificationHistoryService.swift */, - 7C7F56B62FFB9FE000847151 /* NotificationRecord.swift */, + 7C7F56B62FFB9FE000847151 /* NotificationHistoryRecord.swift */, ); path = NotificationHistory; sourceTree = ""; @@ -5654,7 +5654,7 @@ 839D71192BEB1570001BC7F7 /* NotificationService.swift in Sources */, 7CA686902FFCF42E007937E9 /* NotificationHistoryError.swift in Sources */, 7C7F56B92FFBA0A700847151 /* AppPath.swift in Sources */, - 7C7F56B82FFBA06200847151 /* NotificationRecord.swift in Sources */, + 7C7F56B82FFBA06200847151 /* NotificationHistoryRecord.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5833,7 +5833,7 @@ 7C01656B300ABE9E0013DD7B /* DepartmentFooterView.swift in Sources */, 7C05D0782FBC29ED00F4D825 /* ShopBenefitViewModel.swift in Sources */, D29F21F32C4FD23F00994554 /* LandItem.swift in Sources */, - 7C7F56B72FFB9FE000847151 /* NotificationRecord.swift in Sources */, + 7C7F56B72FFB9FE000847151 /* NotificationHistoryRecord.swift in Sources */, D20AB95A2C55369F006BC684 /* NotiAPI.swift in Sources */, 7C457AE72FCDE7240011D338 /* SwiftUIViewModelProtocol.swift in Sources */, 833D9C392C7058C300982145 /* NoticeListRepository.swift in Sources */, @@ -6440,7 +6440,7 @@ 7C8BFCE12FD04B8000963679 /* HomeDiningItem.swift in Sources */, 7C8BFCE32FD04B8000963679 /* HomeHeader.swift in Sources */, 7CF77B4430027321003642C1 /* TimeTableView.swift in Sources */, - 7C8BFCE42FD04B8000963679 /* NotificationItem.swift in Sources */, + 7C8BFCE42FD04B8000963679 /* NotificationHistoryItem.swift in Sources */, 7C8BFCE52FD04B8000963679 /* CategoryModels.swift in Sources */, 7C8BFCF12FD04B800963679 /* HomeCounts.swift in Sources */, D2908D262BB8D11A0008F908 /* SemesterDto.swift in Sources */, From 54f6fa646260525af86abe36fd37a07dcc47ef25 Mon Sep 17 00:00:00 2001 From: GI JEONG HONG Date: Wed, 19 Aug 2026 23:32:02 +0900 Subject: [PATCH 3/5] =?UTF-8?q?refactor:=20NotificationHistoryItem?= =?UTF-8?q?=EC=9D=98=20=EB=A1=9C=EA=B9=85=20value=EB=A5=BC=20NotificationH?= =?UTF-8?q?ostoryItem=EC=9D=98=20computed=20property=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/Home/NotificationHistoryItem.swift | 19 +++++++++++++++ .../NotificationViewController.swift | 23 +------------------ .../Notification/NotificationViewModel.swift | 14 ++++++++--- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/Koin/Domain/Model/Home/NotificationHistoryItem.swift b/Koin/Domain/Model/Home/NotificationHistoryItem.swift index f2c324eb7..1a42978c2 100644 --- a/Koin/Domain/Model/Home/NotificationHistoryItem.swift +++ b/Koin/Domain/Model/Home/NotificationHistoryItem.swift @@ -19,6 +19,25 @@ struct NotificationHistoryItem { } extension NotificationHistoryItem { + var logValue: String? { + switch appPath { + case .shop: + return "주변상점" + case .dining: + return "식단" + case .keyword: + return "키워드알림" + case .chat: + return "분실물 채팅" + case .callvan: + return "콜밴팟" + case .callvanChat: + return "콜밴팟 채팅" + default: + return nil + } + } + init?(from record: NotificationHistoryRecord) { guard let icon = NotificationHistoryItem.icon(for: record.category) else { return nil diff --git a/Koin/Presentation/Home/Notification/NotificationViewController.swift b/Koin/Presentation/Home/Notification/NotificationViewController.swift index 135430e86..785ae8f82 100644 --- a/Koin/Presentation/Home/Notification/NotificationViewController.swift +++ b/Koin/Presentation/Home/Notification/NotificationViewController.swift @@ -83,7 +83,7 @@ private extension NotificationViewController { notificationTableView.tapNotificationPublisher .sink { [weak self] item in - self?.inputSubject.send(.markAsRead(id: item.id)) + self?.inputSubject.send(.selectNotification(id: id)) self?.makeLogEvent(notification: item) self?.handleNavigation(item) } @@ -91,27 +91,6 @@ private extension NotificationViewController { } } -extension NotificationViewController { - private func makeLogEvent(notification: NotificationItem) { - let logValue: String - switch notification.appPath { - case .shop: - logValue = "주변상점" - case .dining: - logValue = "식단" - case .keyword: - logValue = "키워드알림" - case .chat: - logValue = "분실물 채팅" - case .callvan: - logValue = "콜밴팟" - case .callvanChat: - logValue = "콜밴팟 채팅" - } - inputSubject.send(.logEvent(EventParameter.EventLabel.Campus.notificationList, .click, logValue)) - } -} - extension NotificationViewController { private func updateStateViews(isEmpty: Bool) { loadingIndicator.stopAnimating() diff --git a/Koin/Presentation/Home/Notification/NotificationViewModel.swift b/Koin/Presentation/Home/Notification/NotificationViewModel.swift index e25518090..b74d2b612 100644 --- a/Koin/Presentation/Home/Notification/NotificationViewModel.swift +++ b/Koin/Presentation/Home/Notification/NotificationViewModel.swift @@ -13,15 +13,16 @@ final class NotificationViewModel: ViewModelProtocol { enum Input { case viewDidLoad case reload + case selectNotification(id: String) case deleteNotification(id: String) case deleteAllNotifications - case markAsRead(id: String) case markAllAsRead case logEvent(EventLabelType, EventParameter.EventCategory, Any) } enum Output { case updateNotifications([NotificationHistoryItem]) + case selectedNotification(NotificationHistoryItem) case showToast(String) } @@ -56,12 +57,12 @@ final class NotificationViewModel: ViewModelProtocol { switch input { case .viewDidLoad, .reload: self?.loadNotifications() + case .selectNotification(let id): + self?.selectNotification(id: id) case .deleteNotification(let id): self?.deleteNotification(id: id) case .deleteAllNotifications: self?.deleteAllNotifications() - case .markAsRead(let id): - self?.markAsRead(id: id) case .markAllAsRead: self?.markAllAsRead() case let .logEvent(label, category, value): @@ -88,6 +89,13 @@ private extension NotificationViewModel { } } } + if let logValue = notification.logValue { + makeLogAnalyticsEvent( + label: EventParameter.EventLabel.Campus.notificationList, + category: .click, + value: logValue + ) + } private func deleteNotification(id: String) { Task { From af38e7791048d974188ceea2b8a2ec097f8dd2cb Mon Sep 17 00:00:00 2001 From: GI JEONG HONG Date: Thu, 20 Aug 2026 00:25:48 +0900 Subject: [PATCH 4/5] =?UTF-8?q?refactor:=20Notification=20=EA=B3=B5?= =?UTF-8?q?=EC=9A=A9=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationViewController.swift | 76 +++------- .../Notification/NotificationViewModel.swift | 50 ++++-- ...tionRowModel+NotificationHistoryItem.swift | 19 +++ .../Models/NotificationRowModel.swift | 15 ++ .../Views/NotificationEmptyView.swift} | 6 +- .../Views/NotificationListView.swift | 142 ++++++++++++++++++ .../NotificationPopUpViewController.swift | 4 +- .../NotificationFooterView.swift | 1 + .../NotificationTableView.swift | 11 +- .../NotificationTableViewCell.swift | 2 +- koin.xcodeproj/project.pbxproj | 58 ++++++- 11 files changed, 299 insertions(+), 85 deletions(-) create mode 100644 Koin/Presentation/Home/Notification/Support/NotificationRowModel+NotificationHistoryItem.swift create mode 100644 Koin/Presentation/Shared/Notification/Models/NotificationRowModel.swift rename Koin/Presentation/{Home/Notification/Subviews/NotificationEmptyBackgroundView.swift => Shared/Notification/Views/NotificationEmptyView.swift} (92%) create mode 100644 Koin/Presentation/Shared/Notification/Views/NotificationListView.swift rename Koin/Presentation/{Home/Notification/Subviews => Shared/Notification/Views}/NotificationPopUpViewController.swift (98%) rename Koin/Presentation/{Home/Notification/Subviews => Shared/Notification/Views}/NotificationTableView/NotificationFooterView.swift (98%) rename Koin/Presentation/{Home/Notification/Subviews => Shared/Notification/Views}/NotificationTableView/NotificationTableView.swift (95%) rename Koin/Presentation/{Home/Notification/Subviews => Shared/Notification/Views}/NotificationTableView/NotificationTableViewCell.swift (98%) diff --git a/Koin/Presentation/Home/Notification/NotificationViewController.swift b/Koin/Presentation/Home/Notification/NotificationViewController.swift index 785ae8f82..f745f104b 100644 --- a/Koin/Presentation/Home/Notification/NotificationViewController.swift +++ b/Koin/Presentation/Home/Notification/NotificationViewController.swift @@ -8,7 +8,6 @@ import UIKit import Combine import SnapKit -import Then final class NotificationViewController: UIViewController { @@ -18,12 +17,7 @@ final class NotificationViewController: UIViewController { private var subscriptions = Set() // MARK: - UI Components - private let notificationTableView = NotificationTableView() - private let refreshControl = UIRefreshControl() - - private let loadingIndicator = UIActivityIndicatorView(style: .medium).then { - $0.hidesWhenStopped = true - } + private let notificationListView = NotificationListView() // MARK: - Initialization init(viewModel: NotificationViewModel) { @@ -39,10 +33,9 @@ final class NotificationViewController: UIViewController { super.viewDidLoad() configureView() configureNavigationBar() - setAddTargets() bind() + notificationListView.startLoading() inputSubject.send(.viewDidLoad) - loadingIndicator.startAnimating() title = "알림" } @@ -63,53 +56,42 @@ private extension NotificationViewController { switch event { case .updateNotifications(let notifications): - self.notificationTableView.update(notifications: notifications) - self.updateStateViews(isEmpty: notifications.isEmpty) + notificationListView.update(items: notifications.map { NotificationRowModel(from: $0) }) + case .selectedNotification(let notification): + handleNavigation(notification) case .showToast(let message): showToastMessage(message: message) } } .store(in: &subscriptions) - notificationTableView.deletePublisher + notificationListView.deletePublisher .sink { [weak self] id in guard let self else { return } self.inputSubject.send(.deleteNotification(id: id)) - self.updateStateViews(isEmpty: self.notificationTableView.isEmpty) self.showToastMessage(message: "알림이 삭제되었습니다.") self.inputSubject.send(.logEvent(EventParameter.EventLabel.Campus.notificationListDelete, .click, "알림 삭제")) } .store(in: &subscriptions) - notificationTableView.tapNotificationPublisher - .sink { [weak self] item in + notificationListView.itemTappedPublisher + .sink { [weak self] id in self?.inputSubject.send(.selectNotification(id: id)) - self?.makeLogEvent(notification: item) - self?.handleNavigation(item) } .store(in: &subscriptions) - } -} -extension NotificationViewController { - private func updateStateViews(isEmpty: Bool) { - loadingIndicator.stopAnimating() - refreshControl.endRefreshing() - - UIView.animate( - withDuration: 0.2, - delay: 0, - options: [.curveEaseInOut, .beginFromCurrentState] - ) { [weak self] in - self?.notificationTableView.backgroundView?.alpha = isEmpty ? 1 : 0 - } + notificationListView.refreshPublisher + .sink { [weak self] in + self?.inputSubject.send(.reload) + } + .store(in: &subscriptions) } } // MARK: - Navigation extension NotificationViewController { - private func handleNavigation(_ item: NotificationItem) { + private func handleNavigation(_ item: NotificationHistoryItem) { guard let uri: String = item.uri, let parsedQuery = parseQuery(uri: uri) else { return @@ -320,21 +302,16 @@ private extension NotificationViewController { showPopUpView() } - @objc func didPullToRefresh() { - inputSubject.send(.reload) - } - private func showPopUpView() { let popUpViewController = NotificationPopUpViewController( markAllAsRead: { [weak self] in self?.inputSubject.send(.markAllAsRead) - self?.notificationTableView.markAllAsRead() + self?.notificationListView.markAllAsRead() self?.inputSubject.send(.logEvent(EventParameter.EventLabel.Campus.notificationListReadAll, .click, "모두 읽음으로 표시")) }, deleteAll: { [weak self] in self?.inputSubject.send(.deleteAllNotifications) - self?.notificationTableView.deleteAll() - self?.updateStateViews(isEmpty: true) + self?.notificationListView.deleteAll() self?.showToastMessage(message: "알림이 삭제되었습니다.") self?.inputSubject.send(.logEvent(EventParameter.EventLabel.Campus.notificationListDeleteAll, .click, "알림 전체 삭제")) } @@ -350,11 +327,6 @@ private extension NotificationViewController { // MARK: - Configure private extension NotificationViewController { - - private func setAddTargets() { - refreshControl.addTarget(self, action: #selector(didPullToRefresh), for: .valueChanged) - } - private func configureNavigationBar() { let rightBarButtonItem = UIBarButtonItem( @@ -374,28 +346,16 @@ private extension NotificationViewController { private func setUpStyles() { view.backgroundColor = UIColor.ColorSystem.Neutral.gray0 - - notificationTableView.refreshControl = refreshControl - - notificationTableView.backgroundView = NotificationEmptyBackgroundView().then { - $0.alpha = 0 - } } private func setUpLayouts() { - [notificationTableView, loadingIndicator].forEach { - view.addSubview($0) - } + view.addSubview(notificationListView) } private func setUpConstraints() { - notificationTableView.snp.makeConstraints { + notificationListView.snp.makeConstraints { $0.top.equalTo(view.safeAreaLayoutGuide.snp.top) $0.leading.trailing.bottom.equalToSuperview() } - - loadingIndicator.snp.makeConstraints { - $0.center.equalToSuperview() - } } } diff --git a/Koin/Presentation/Home/Notification/NotificationViewModel.swift b/Koin/Presentation/Home/Notification/NotificationViewModel.swift index b74d2b612..d80637479 100644 --- a/Koin/Presentation/Home/Notification/NotificationViewModel.swift +++ b/Koin/Presentation/Home/Notification/NotificationViewModel.swift @@ -33,6 +33,7 @@ final class NotificationViewModel: ViewModelProtocol { private let updateNotificationHistoryUseCase: UpdateNotificationHistoryUseCase private let logAnalyticsEventUseCase: LogAnalyticsEventUseCase private let outputSubject = PassthroughSubject() + private var notificationHistoryItems: [NotificationHistoryItem] = [] private var subscriptions = Set() // MARK: - Initializer @@ -83,41 +84,72 @@ private extension NotificationViewModel { Task { do { let notifications = try await fetchNotificationHistoryUseCase.execute() + self.notificationHistoryItems = notifications outputSubject.send(.updateNotifications(notifications)) } catch { outputSubject.send(.showToast(error.localizedDescription)) } } } - if let logValue = notification.logValue { - makeLogAnalyticsEvent( - label: EventParameter.EventLabel.Campus.notificationList, - category: .click, - value: logValue - ) + + private func selectNotification(id: String) { + guard let notification = notificationHistoryItems.first(where: { $0.id == id }), + let logValue = notification.logValue else { + return } + markAsRead(id: id) + + outputSubject.send(.selectedNotification(notification)) + + makeLogAnalyticsEvent( + label: EventParameter.EventLabel.Campus.notificationList, + category: .click, + value: logValue + ) + } private func deleteNotification(id: String) { + notificationHistoryItems.removeAll { $0.id == id } Task { - try? await deleteNotificationHistoryUseCase.delete(id: id) + do { + try await deleteNotificationHistoryUseCase.delete(id: id) + } catch { + outputSubject.send(.showToast(error.localizedDescription)) + } } } private func deleteAllNotifications() { + notificationHistoryItems.removeAll() Task { - try? await deleteNotificationHistoryUseCase.deleteAll() + do { + try await deleteNotificationHistoryUseCase.deleteAll() + } catch { + outputSubject.send(.showToast(error.localizedDescription)) + } } } private func markAsRead(id: String) { + if let index = notificationHistoryItems.firstIndex(where: { $0.id == id }) { + notificationHistoryItems[index].isRead = true + } + Task { try? await updateNotificationHistoryUseCase.markAsRead(id: id) } } private func markAllAsRead() { + for index in notificationHistoryItems.indices { + notificationHistoryItems[index].isRead = true + } Task { - try? await updateNotificationHistoryUseCase.markAllAsRead() + do { + try await updateNotificationHistoryUseCase.markAllAsRead() + } catch { + outputSubject.send(.showToast(error.localizedDescription)) + } } } diff --git a/Koin/Presentation/Home/Notification/Support/NotificationRowModel+NotificationHistoryItem.swift b/Koin/Presentation/Home/Notification/Support/NotificationRowModel+NotificationHistoryItem.swift new file mode 100644 index 000000000..26b2018b4 --- /dev/null +++ b/Koin/Presentation/Home/Notification/Support/NotificationRowModel+NotificationHistoryItem.swift @@ -0,0 +1,19 @@ +// +// NotificationRowModel+NotificationHistoryItem.swift +// koin +// +// Created by 홍기정 on 8/19/26. +// + +extension NotificationRowModel { + init(from item: NotificationHistoryItem) { + self.init( + id: item.id, + isRead: item.isRead, + icon: item.icon, + title: item.title, + content: item.content, + dateText: item.dateText + ) + } +} diff --git a/Koin/Presentation/Shared/Notification/Models/NotificationRowModel.swift b/Koin/Presentation/Shared/Notification/Models/NotificationRowModel.swift new file mode 100644 index 000000000..90ca31604 --- /dev/null +++ b/Koin/Presentation/Shared/Notification/Models/NotificationRowModel.swift @@ -0,0 +1,15 @@ +// +// NotificationRowModel.swift +// koin +// +// Created by 홍기정 on 8/19/26. +// + +struct NotificationRowModel { + let id: String + var isRead: Bool + let icon: ImageAsset + let title: String + let content: String + let dateText: String +} diff --git a/Koin/Presentation/Home/Notification/Subviews/NotificationEmptyBackgroundView.swift b/Koin/Presentation/Shared/Notification/Views/NotificationEmptyView.swift similarity index 92% rename from Koin/Presentation/Home/Notification/Subviews/NotificationEmptyBackgroundView.swift rename to Koin/Presentation/Shared/Notification/Views/NotificationEmptyView.swift index f0c2525f0..934ea7b90 100644 --- a/Koin/Presentation/Home/Notification/Subviews/NotificationEmptyBackgroundView.swift +++ b/Koin/Presentation/Shared/Notification/Views/NotificationEmptyView.swift @@ -1,5 +1,5 @@ // -// NotificationEmptyBackgroundView.swift +// NotificationEmptyView.swift // koin // // Created by 홍기정 on 6/3/26. @@ -9,7 +9,7 @@ import UIKit import SnapKit import Then -final class NotificationEmptyBackgroundView: UIView { +final class NotificationEmptyView: UIView { // MARK: - UI Components private let layoutGuide = UILayoutGuide() @@ -27,7 +27,7 @@ final class NotificationEmptyBackgroundView: UIView { } } -extension NotificationEmptyBackgroundView { +extension NotificationEmptyView { private func configureView() { setUpStyles() diff --git a/Koin/Presentation/Shared/Notification/Views/NotificationListView.swift b/Koin/Presentation/Shared/Notification/Views/NotificationListView.swift new file mode 100644 index 000000000..c29ababb1 --- /dev/null +++ b/Koin/Presentation/Shared/Notification/Views/NotificationListView.swift @@ -0,0 +1,142 @@ +// +// NotificationListView.swift +// koin +// +// Created by 홍기정 on 8/19/26. +// + +import UIKit +import Combine +import SnapKit +import Then + +final class NotificationListView: UIView { + + // MARK: - Properties + let refreshPublisher = PassthroughSubject() + let itemTappedPublisher = PassthroughSubject() + let deletePublisher = PassthroughSubject() + private var subscriptions = Set() + + // MARK: - UI Components + private let tableView = NotificationTableView() + private let refreshControl = UIRefreshControl() + private let loadingIndicator = UIActivityIndicatorView(style: .medium) + private let emptyView = NotificationEmptyView() + + // MARK: - Initialization + override init(frame: CGRect) { + super.init(frame: frame) + configureView() + bind() + } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Public + func startLoading() { + loadingIndicator.startAnimating() + } + + func update(items: [NotificationRowModel]) { + tableView.update(notifications: items) + updateStateViews(isEmpty: items.isEmpty) + } + + func markAllAsRead() { + tableView.markAllAsRead() + } + + func deleteAll() { + tableView.deleteAll() + updateStateViews(isEmpty: true) + } +} + +// MARK: - Bind + +private extension NotificationListView { + private func bind() { + tableView.tapNotificationPublisher + .sink { [weak self] id in + self?.itemTappedPublisher.send(id) + } + .store(in: &subscriptions) + + tableView.deletePublisher + .sink { [weak self] id in + guard let self else { return } + updateStateViews(isEmpty: tableView.isEmpty) + deletePublisher.send(id) + } + .store(in: &subscriptions) + } +} + +// MARK: - State + +private extension NotificationListView { + private func updateStateViews(isEmpty: Bool) { + loadingIndicator.stopAnimating() + refreshControl.endRefreshing() + + UIView.animate( + withDuration: 0.2, + delay: 0, + options: [.curveEaseInOut, .beginFromCurrentState] + ) { [weak self] in + self?.tableView.backgroundView?.alpha = isEmpty ? 1 : 0 + } + } +} + +// MARK: - Configure + +private extension NotificationListView { + private func configureView() { + setUpAddTargets() + setUpStyles() + setUpLayouts() + setUpConstraints() + } + + private func setUpStyles() { + backgroundColor = .appColor(.neutral0) + + loadingIndicator.do { + $0.hidesWhenStopped = true + } + + tableView.do { + $0.refreshControl = refreshControl + $0.backgroundView = emptyView + } + + emptyView.do { + $0.alpha = 0 + } + } + + private func setUpLayouts() { + [tableView, loadingIndicator].forEach { + addSubview($0) + } + } + private func setUpConstraints() { + tableView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + loadingIndicator.snp.makeConstraints { + $0.center.equalToSuperview() + } + } + + private func setUpAddTargets() { + refreshControl.addTarget(self, action: #selector(didPullToRefresh), for: .valueChanged) + } + + @objc private func didPullToRefresh() { + refreshPublisher.send() + } +} diff --git a/Koin/Presentation/Home/Notification/Subviews/NotificationPopUpViewController.swift b/Koin/Presentation/Shared/Notification/Views/NotificationPopUpViewController.swift similarity index 98% rename from Koin/Presentation/Home/Notification/Subviews/NotificationPopUpViewController.swift rename to Koin/Presentation/Shared/Notification/Views/NotificationPopUpViewController.swift index 05f6bc1c3..44f14c71f 100644 --- a/Koin/Presentation/Home/Notification/Subviews/NotificationPopUpViewController.swift +++ b/Koin/Presentation/Shared/Notification/Views/NotificationPopUpViewController.swift @@ -18,8 +18,8 @@ final class NotificationPopUpViewController: UIViewController { private let deleteAllButton = UIButton() // MARK: - Properties - @objc private let markAllAsRead: ()->Void - @objc private let deleteAll: ()->Void + private let markAllAsRead: ()->Void + private let deleteAll: ()->Void private var minimizedTransform: CGAffineTransform { let scale = 0.4 diff --git a/Koin/Presentation/Home/Notification/Subviews/NotificationTableView/NotificationFooterView.swift b/Koin/Presentation/Shared/Notification/Views/NotificationTableView/NotificationFooterView.swift similarity index 98% rename from Koin/Presentation/Home/Notification/Subviews/NotificationTableView/NotificationFooterView.swift rename to Koin/Presentation/Shared/Notification/Views/NotificationTableView/NotificationFooterView.swift index 6b46ab2d0..a6ac8bedb 100644 --- a/Koin/Presentation/Home/Notification/Subviews/NotificationTableView/NotificationFooterView.swift +++ b/Koin/Presentation/Shared/Notification/Views/NotificationTableView/NotificationFooterView.swift @@ -11,6 +11,7 @@ import Then final class NotificationFooterView: UIView { + // MARK: - UI Components private let label = UILabel() override init(frame: CGRect) { diff --git a/Koin/Presentation/Home/Notification/Subviews/NotificationTableView/NotificationTableView.swift b/Koin/Presentation/Shared/Notification/Views/NotificationTableView/NotificationTableView.swift similarity index 95% rename from Koin/Presentation/Home/Notification/Subviews/NotificationTableView/NotificationTableView.swift rename to Koin/Presentation/Shared/Notification/Views/NotificationTableView/NotificationTableView.swift index 28541a1d4..e08a3c77f 100644 --- a/Koin/Presentation/Home/Notification/Subviews/NotificationTableView/NotificationTableView.swift +++ b/Koin/Presentation/Shared/Notification/Views/NotificationTableView/NotificationTableView.swift @@ -20,13 +20,13 @@ final class NotificationTableView: UITableView { // MARK: - Publisher let deletePublisher = PassthroughSubject() - let tapNotificationPublisher = PassthroughSubject() + let tapNotificationPublisher = PassthroughSubject() // MARK: - UI Components private let realFooterView = NotificationFooterView() // MARK: - Properties - private var notifications: [NotificationItem] = [] + private var notifications: [NotificationRowModel] = [] var isEmpty: Bool { notifications.isEmpty } @@ -43,7 +43,7 @@ final class NotificationTableView: UITableView { } // MARK: - Public - func update(notifications: [NotificationItem]) { + func update(notifications: [NotificationRowModel]) { performBatchUpdates { self.notifications = notifications recalculateFooterHeightIfNeeded() @@ -173,8 +173,9 @@ extension NotificationTableView: UITableViewDelegate { guard notifications.indices.contains(indexPath.row) else { return } - tapNotificationPublisher.send(notifications[indexPath.row]) - didSelectNotification(id: notifications[indexPath.row].id) + let id = notifications[indexPath.row].id + tapNotificationPublisher.send(id) + didSelectNotification(id: id) } func tableView( diff --git a/Koin/Presentation/Home/Notification/Subviews/NotificationTableView/NotificationTableViewCell.swift b/Koin/Presentation/Shared/Notification/Views/NotificationTableView/NotificationTableViewCell.swift similarity index 98% rename from Koin/Presentation/Home/Notification/Subviews/NotificationTableView/NotificationTableViewCell.swift rename to Koin/Presentation/Shared/Notification/Views/NotificationTableView/NotificationTableViewCell.swift index ca19ef7cd..fea8fb9ed 100644 --- a/Koin/Presentation/Home/Notification/Subviews/NotificationTableView/NotificationTableViewCell.swift +++ b/Koin/Presentation/Shared/Notification/Views/NotificationTableView/NotificationTableViewCell.swift @@ -27,7 +27,7 @@ final class NotificationTableViewCell: UITableViewCell { } // MARK: - Public - func configure(item: NotificationItem) { + func configure(item: NotificationRowModel) { iconImageView.image = .appImage(asset: item.icon)?.withRenderingMode(.alwaysTemplate) titleLabel.text = item.title contentLabel.text = item.content diff --git a/koin.xcodeproj/project.pbxproj b/koin.xcodeproj/project.pbxproj index 14f123bcd..6731f5bfc 100644 --- a/koin.xcodeproj/project.pbxproj +++ b/koin.xcodeproj/project.pbxproj @@ -254,7 +254,7 @@ 7C8A000F2FD200000011D338 /* DiningIndicatorChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8A00102FD200000011D338 /* DiningIndicatorChip.swift */; }; 7C8A941E2FD0A94F00DEA6F5 /* NotificationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8A941D2FD0A94F00DEA6F5 /* NotificationViewModel.swift */; }; 7C8A941F2FD0A94F00DEA6F5 /* NotificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8A941C2FD0A94F00DEA6F5 /* NotificationViewController.swift */; }; - 7C8A94222FD0A96400DEA6F5 /* NotificationEmptyBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8A94212FD0A96400DEA6F5 /* NotificationEmptyBackgroundView.swift */; }; + 7C8A94222FD0A96400DEA6F5 /* NotificationEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8A94212FD0A96400DEA6F5 /* NotificationEmptyView.swift */; }; 7C8A94272FD0A97400DEA6F5 /* NotificationFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8A94242FD0A97400DEA6F5 /* NotificationFooterView.swift */; }; 7C8A94282FD0A97400DEA6F5 /* NotificationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8A94262FD0A97400DEA6F5 /* NotificationTableViewCell.swift */; }; 7C8A94292FD0A97400DEA6F5 /* NotificationTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8A94252FD0A97400DEA6F5 /* NotificationTableView.swift */; }; @@ -763,6 +763,9 @@ B68D2CEB2EA50C1E00F3B479 /* UIViewController+Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68D2CEA2EA50C1E00F3B479 /* UIViewController+Toast.swift */; }; B6A0D62A2EEB01E600A19521 /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = B6A0D6292EEB01E600A19521 /* Debug.xcconfig */; }; B6A0D62C2EEB01F100A19521 /* Release.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = B6A0D62B2EEB01F100A19521 /* Release.xcconfig */; }; + D19A00013000000000000002 /* NotificationRowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19A00013000000000000001 /* NotificationRowModel.swift */; }; + D19A00013000000000000004 /* NotificationListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19A00013000000000000003 /* NotificationListView.swift */; }; + D19A00013000000000000006 /* NotificationRowModel+NotificationHistoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19A00013000000000000005 /* NotificationRowModel+NotificationHistoryItem.swift */; }; D2009F762C17270F00211D1B /* CalendarDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2009F752C17270F00211D1B /* CalendarDate.swift */; }; D2009F7F2C1784DA00211D1B /* MockAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2009F7E2C1784DA00211D1B /* MockAnalyticsService.swift */; }; D2078BF62BC4CCEB00A39861 /* EventDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2078BF52BC4CCEB00A39861 /* EventDto.swift */; }; @@ -1272,7 +1275,7 @@ 7C8A00102FD200000011D338 /* DiningIndicatorChip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiningIndicatorChip.swift; sourceTree = ""; }; 7C8A941C2FD0A94F00DEA6F5 /* NotificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewController.swift; sourceTree = ""; }; 7C8A941D2FD0A94F00DEA6F5 /* NotificationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewModel.swift; sourceTree = ""; }; - 7C8A94212FD0A96400DEA6F5 /* NotificationEmptyBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationEmptyBackgroundView.swift; sourceTree = ""; }; + 7C8A94212FD0A96400DEA6F5 /* NotificationEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationEmptyView.swift; sourceTree = ""; }; 7C8A94242FD0A97400DEA6F5 /* NotificationFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFooterView.swift; sourceTree = ""; }; 7C8A94252FD0A97400DEA6F5 /* NotificationTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationTableView.swift; sourceTree = ""; }; 7C8A94262FD0A97400DEA6F5 /* NotificationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationTableViewCell.swift; sourceTree = ""; }; @@ -1768,6 +1771,9 @@ B68D2CEA2EA50C1E00F3B479 /* UIViewController+Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Toast.swift"; sourceTree = ""; }; B6A0D6292EEB01E600A19521 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; B6A0D62B2EEB01F100A19521 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + D19A00013000000000000001 /* NotificationRowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRowModel.swift; sourceTree = ""; }; + D19A00013000000000000003 /* NotificationListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationListView.swift; sourceTree = ""; }; + D19A00013000000000000005 /* NotificationRowModel+NotificationHistoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationRowModel+NotificationHistoryItem.swift"; sourceTree = ""; }; D2009F752C17270F00211D1B /* CalendarDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarDate.swift; sourceTree = ""; }; D2009F7E2C1784DA00211D1B /* MockAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAnalyticsService.swift; sourceTree = ""; }; D2078BF52BC4CCEB00A39861 /* EventDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDto.swift; sourceTree = ""; }; @@ -2379,7 +2385,7 @@ 7C457B282FCE1BA80011D338 /* Notification */ = { isa = PBXGroup; children = ( - 7C8A94202FD0A95100DEA6F5 /* Subviews */, + D19A0001300000000000000A /* Support */, 7C8A941C2FD0A94F00DEA6F5 /* NotificationViewController.swift */, 7C8A941D2FD0A94F00DEA6F5 /* NotificationViewModel.swift */, ); @@ -2792,14 +2798,15 @@ path = Home; sourceTree = ""; }; - 7C8A94202FD0A95100DEA6F5 /* Subviews */ = { + 7C8A94202FD0A95100DEA6F5 /* Views */ = { isa = PBXGroup; children = ( 7C8A94232FD0A96700DEA6F5 /* NotificationTableView */, - 7C8A94212FD0A96400DEA6F5 /* NotificationEmptyBackgroundView.swift */, + 7C8A94212FD0A96400DEA6F5 /* NotificationEmptyView.swift */, + D19A00013000000000000003 /* NotificationListView.swift */, 7C7F56B32FFB6A6400847151 /* NotificationPopUpViewController.swift */, ); - path = Subviews; + path = Views; sourceTree = ""; }; 7C8A94232FD0A96700DEA6F5 /* NotificationTableView */ = { @@ -4307,6 +4314,7 @@ isa = PBXGroup; children = ( 7C86751030062FF3003CB942 /* Core */, + D19A00013000000000000007 /* Shared */, 7C457B2A2FCE1BA80011D338 /* Home */, 7C016541300A9FF80013DD7B /* Department */, 7CED8EF42EF2D1F900457128 /* LostItem */, @@ -4654,6 +4662,39 @@ path = Configuration; sourceTree = ""; }; + D19A00013000000000000007 /* Shared */ = { + isa = PBXGroup; + children = ( + D19A00013000000000000008 /* Notification */, + ); + path = Shared; + sourceTree = ""; + }; + D19A00013000000000000008 /* Notification */ = { + isa = PBXGroup; + children = ( + D19A00013000000000000009 /* Models */, + 7C8A94202FD0A95100DEA6F5 /* Views */, + ); + path = Notification; + sourceTree = ""; + }; + D19A00013000000000000009 /* Models */ = { + isa = PBXGroup; + children = ( + D19A00013000000000000001 /* NotificationRowModel.swift */, + ); + path = Models; + sourceTree = ""; + }; + D19A0001300000000000000A /* Support */ = { + isa = PBXGroup; + children = ( + D19A00013000000000000005 /* NotificationRowModel+NotificationHistoryItem.swift */, + ); + path = Support; + sourceTree = ""; + }; D2009F7D2C1784C000211D1B /* MockService */ = { isa = PBXGroup; children = ( @@ -6441,6 +6482,9 @@ 7C8BFCE32FD04B8000963679 /* HomeHeader.swift in Sources */, 7CF77B4430027321003642C1 /* TimeTableView.swift in Sources */, 7C8BFCE42FD04B8000963679 /* NotificationHistoryItem.swift in Sources */, + D19A00013000000000000002 /* NotificationRowModel.swift in Sources */, + D19A00013000000000000004 /* NotificationListView.swift in Sources */, + D19A00013000000000000006 /* NotificationRowModel+NotificationHistoryItem.swift in Sources */, 7C8BFCE52FD04B8000963679 /* CategoryModels.swift in Sources */, 7C8BFCF12FD04B800963679 /* HomeCounts.swift in Sources */, D2908D262BB8D11A0008F908 /* SemesterDto.swift in Sources */, @@ -6514,7 +6558,7 @@ 7C333EA53030429B009D2B89 /* ModalButtonView.swift in Sources */, D22C54712C6A2A60000826DA /* FetchShopReviewUseCase.swift in Sources */, 7C6403D3300BE8A100A9589F /* DepartmentEmptyView.swift in Sources */, - 7C8A94222FD0A96400DEA6F5 /* NotificationEmptyBackgroundView.swift in Sources */, + 7C8A94222FD0A96400DEA6F5 /* NotificationEmptyView.swift in Sources */, 8354BE522C7A0566009D4D7A /* CoreDataService.swift in Sources */, D2D7C0D52C28298500C85A85 /* FetchLandListUseCase.swift in Sources */, 7C17EEB22EBF7175008BCA89 /* FetchSearchShopUseCase.swift in Sources */, From 1326e9190dd1c131f090d412ed4bb1704e7f2332 Mon Sep 17 00:00:00 2001 From: GI JEONG HONG Date: Thu, 20 Aug 2026 01:10:30 +0900 Subject: [PATCH 5/5] =?UTF-8?q?refactor:=20CallVanChat=20=EA=B3=B5?= =?UTF-8?q?=EC=9A=A9=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CallVanChatViewController.swift | 168 ++++-------------- .../Support/ChatListModel+CallVanChat.swift | 31 ++++ .../Shared/Chat/Models/ChatListModel.swift | 11 ++ .../Chat/Models/ChatMessageRowModel.swift | 28 +++ .../Shared/Chat/Views/ChatInputView.swift | 151 ++++++++++++++++ .../Shared/Chat/Views/ChatListView.swift | 117 ++++++++++++ .../ChatTableView/ChatDateHeaderView.swift} | 6 +- .../Views/ChatTableView/ChatLeftCell.swift} | 37 ++-- .../Views/ChatTableView/ChatRightCell.swift} | 37 ++-- .../Views/ChatTableView/ChatTableView.swift} | 39 ++-- koin.xcodeproj/project.pbxproj | 98 +++++++--- 11 files changed, 505 insertions(+), 218 deletions(-) create mode 100644 Koin/Presentation/CallVan/CallVanChat/Support/ChatListModel+CallVanChat.swift create mode 100644 Koin/Presentation/Shared/Chat/Models/ChatListModel.swift create mode 100644 Koin/Presentation/Shared/Chat/Models/ChatMessageRowModel.swift create mode 100644 Koin/Presentation/Shared/Chat/Views/ChatInputView.swift create mode 100644 Koin/Presentation/Shared/Chat/Views/ChatListView.swift rename Koin/Presentation/{CallVan/CallVanChat/Subviews/CallVanChatTableView/CallVanChatDateHeaderView.swift => Shared/Chat/Views/ChatTableView/ChatDateHeaderView.swift} (91%) rename Koin/Presentation/{CallVan/CallVanChat/Subviews/CallVanChatTableView/CallVanChatLeftCell.swift => Shared/Chat/Views/ChatTableView/ChatLeftCell.swift} (89%) rename Koin/Presentation/{CallVan/CallVanChat/Subviews/CallVanChatTableView/CallVanChatRightCell.swift => Shared/Chat/Views/ChatTableView/ChatRightCell.swift} (87%) rename Koin/Presentation/{CallVan/CallVanChat/Subviews/CallVanChatTableView/CallVanChatTableView.swift => Shared/Chat/Views/ChatTableView/ChatTableView.swift} (67%) diff --git a/Koin/Presentation/CallVan/CallVanChat/CallVanChatViewController.swift b/Koin/Presentation/CallVan/CallVanChat/CallVanChatViewController.swift index 0b5e974e4..3dc7395d6 100644 --- a/Koin/Presentation/CallVan/CallVanChat/CallVanChatViewController.swift +++ b/Koin/Presentation/CallVan/CallVanChat/CallVanChatViewController.swift @@ -17,7 +17,6 @@ final class CallVanChatViewController: UIViewController { private let inputSubject = PassthroughSubject() private let viewModel: CallVanChatViewModel private var subscriptions: Set = [] - private let textViewPlaceHolder = "메시지 보내기" // MARK: - TitleView private lazy var titleLabel = UILabel() @@ -27,11 +26,7 @@ final class CallVanChatViewController: UIViewController { private lazy var titleView = UIView() // MARK: - UI Components - private let callVanChatTableView = CallVanChatTableView() - private let wrapperView = UIView() - private let sendImageButton = UIButton() - private let messageTextView = UITextView() - private let sendMessageButton = UIButton() + private let chatListView = ChatListView() // MARK: - Initializer init(viewModel: CallVanChatViewModel) { @@ -46,10 +41,7 @@ final class CallVanChatViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() configureView() - setDelegate() - setAddTargets() bind() - setGesture() configureNavigationBar(style: .empty) inputSubject.send(.viewDidLoad) } @@ -71,55 +63,47 @@ final class CallVanChatViewController: UIViewController { case let .showToast(message): showToastMessage(message: message) case let .update(callVanChat): - callVanChatTableView.configure(callVanChat: callVanChat) + chatListView.update(model: ChatListModel(from: callVanChat)) case let .updateData(callVanData): configureNavigationBar(callVanData) } }.store(in: &subscriptions) - - callVanChatTableView.imageTappedPublisher.receive(on: DispatchQueue.main).sink { [weak self] imageUrl in - let zoomedImageViewController = ZoomedImageViewControllerB(shouldShowTitle: false) - zoomedImageViewController.configure(url: imageUrl) - zoomedImageViewController.modalTransitionStyle = .crossDissolve - zoomedImageViewController.modalPresentationStyle = .overFullScreen - self?.present(zoomedImageViewController, animated: true) - }.store(in: &subscriptions) - } -} -extension CallVanChatViewController { - - private func setGesture() { - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapAround)) - tapGesture.cancelsTouchesInView = false - callVanChatTableView.addGestureRecognizer(tapGesture) - } - - private func setAddTargets() { - sendImageButton.addTarget(self, action: #selector(sendImageButtonTapped), for: .touchUpInside) - sendMessageButton.addTarget(self, action: #selector(sendMessageButtonTapped), for: .touchUpInside) - } - - @objc private func didTapAround() { - dismissKeyboard() - } - - @objc private func sendMessageButtonTapped() { - guard messageTextView.textColor == UIColor.appColor(.neutral800) else { - return - } - let text = messageTextView.text.trimmingCharacters(in: .whitespacesAndNewlines) - if !text.isEmpty { - inputSubject.send(.sendMessage(text)) - inputSubject.send(.logEvent(label: EventParameter.EventLabel.Campus.callvanChatSend, category: .click, value: "")) - messageTextView.text = "" - } + chatListView.messageSendPublisher + .sink { [weak self] message in + self?.inputSubject.send(.sendMessage(message)) + self?.inputSubject.send( + .logEvent( + label: EventParameter.EventLabel.Campus.callvanChatSend, + category: .click, + value: "" + ) + ) + } + .store(in: &subscriptions) + + chatListView.imageSendTappedPublisher + .sink { [weak self] in + self?.presentImagePicker() + } + .store(in: &subscriptions) + + chatListView.imageTappedPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] imageUrl in + let zoomedImageViewController = ZoomedImageViewControllerB(shouldShowTitle: false) + zoomedImageViewController.configure(url: imageUrl) + zoomedImageViewController.modalTransitionStyle = .crossDissolve + zoomedImageViewController.modalPresentationStyle = .overFullScreen + self?.present(zoomedImageViewController, animated: true) + } + .store(in: &subscriptions) } } extension CallVanChatViewController: PHPickerViewControllerDelegate { - @objc private func sendImageButtonTapped() { + private func presentImagePicker() { var configuration = PHPickerConfiguration() configuration.filter = .images configuration.selectionLimit = 1 @@ -153,27 +137,6 @@ extension CallVanChatViewController: PHPickerViewControllerDelegate { } } -extension CallVanChatViewController: UITextViewDelegate { - - private func setDelegate() { - messageTextView.delegate = self - } - - func textViewDidBeginEditing(_ textView: UITextView) { - if textView.textColor == UIColor.appColor(.neutral500) { - textView.text = "" - textView.textColor = UIColor.appColor(.neutral800) - } - } - - func textViewDidEndEditing(_ textView: UITextView) { - if textView.text.trimmingCharacters(in: .whitespacesAndNewlines).count == 0 { - textView.text = textViewPlaceHolder - textView.textColor = UIColor.appColor(.neutral500) - } - } -} - extension CallVanChatViewController { private func configureNavigationBar(_ callVanData: CallVanData) { @@ -213,41 +176,6 @@ extension CallVanChatViewController { private func setUpStyles() { view.backgroundColor = UIColor.appColor(.neutral100) - - // MARK: - UI Components - callVanChatTableView.do { - $0.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi)) - $0.backgroundColor = .white - $0.separatorStyle = .none - $0.showsVerticalScrollIndicator = false - } - wrapperView.do { - $0.backgroundColor = UIColor.appColor(.neutral100) - } - sendImageButton.do { - $0.setImage(UIImage.appImage(asset: .callVanSendImage), for: .normal) - $0.backgroundColor = UIColor.appColor(.neutral0) - $0.layer.cornerRadius = 12 - $0.clipsToBounds = true - } - messageTextView.do { - let font = UIFont.appFont(.pretendardRegular, size: 12) - let height: CGFloat = 32 - let topBottomInset = (height - font.lineHeight) / 2 - - $0.isScrollEnabled = false - $0.font = font - $0.backgroundColor = UIColor.appColor(.neutral0) - $0.textContainerInset = UIEdgeInsets(top: topBottomInset, left: 16, bottom: topBottomInset, right: 16) - $0.layer.cornerRadius = 12 - $0.text = textViewPlaceHolder - $0.textColor = UIColor.appColor(.neutral500) - } - sendMessageButton.do { - $0.setImage(UIImage.appImage(asset: .callVanSendMessage), for: .normal) - $0.layer.cornerRadius = 12 - $0.clipsToBounds = true - } } private func setUpLayouts() { @@ -260,12 +188,7 @@ extension CallVanChatViewController { } // MARK: - UI Components - [sendImageButton, messageTextView, sendMessageButton].forEach { - wrapperView.addSubview($0) - } - [callVanChatTableView, wrapperView].forEach { - view.addSubview($0) - } + view.addSubview(chatListView) } private func setUpConstraints() { @@ -286,30 +209,9 @@ extension CallVanChatViewController { } // MARK: - UI Components - callVanChatTableView.snp.makeConstraints { + chatListView.snp.makeConstraints { $0.top.equalTo(view.safeAreaLayoutGuide) - $0.leading.trailing.equalToSuperview() - $0.bottom.equalTo(wrapperView.snp.top) - } - wrapperView.snp.makeConstraints { - $0.leading.trailing.equalToSuperview() - $0.bottom.equalTo(view.keyboardLayoutGuide.snp.top) - } - sendImageButton.snp.makeConstraints { - $0.size.equalTo(32) - $0.top.equalTo(wrapperView).offset(8) - $0.leading.equalTo(wrapperView).offset(24) - } - sendMessageButton.snp.makeConstraints { - $0.size.equalTo(32) - $0.top.equalTo(wrapperView).offset(8) - $0.trailing.equalTo(wrapperView).offset(-24) - } - messageTextView.snp.makeConstraints { - $0.top.bottom.equalTo(wrapperView).inset(8) - $0.leading.equalTo(sendImageButton.snp.trailing).offset(8) - $0.trailing.equalTo(sendMessageButton.snp.leading).offset(-8) - $0.bottom.greaterThanOrEqualTo(sendImageButton) + $0.leading.trailing.bottom.equalToSuperview() } } } diff --git a/Koin/Presentation/CallVan/CallVanChat/Support/ChatListModel+CallVanChat.swift b/Koin/Presentation/CallVan/CallVanChat/Support/ChatListModel+CallVanChat.swift new file mode 100644 index 000000000..0181c2da9 --- /dev/null +++ b/Koin/Presentation/CallVan/CallVanChat/Support/ChatListModel+CallVanChat.swift @@ -0,0 +1,31 @@ +// +// ChatListModel+CallVanChat.swift +// koin +// +// Created by 홍기정 on 8/20/26. +// + +extension ChatListModel { + init(from chat: CallVanChat) { + self.init( + dates: chat.dates, + messages: chat.messages.map { messages in + messages.map { ChatMessageRowModel(from: $0) } + } + ) + } +} + +extension ChatMessageRowModel { + init(from message: CallVanChatMessage) { + self.init( + alignment: message.isMine ? .right : .left, + content: message.isImage ? .image(message.content) : .text(message.content), + senderNickname: message.senderNickname, + timeText: message.time, + showsProfile: message.showProfile, + isLeftUser: message.isLeftUser, + profileImage: message.profileImage + ) + } +} diff --git a/Koin/Presentation/Shared/Chat/Models/ChatListModel.swift b/Koin/Presentation/Shared/Chat/Models/ChatListModel.swift new file mode 100644 index 000000000..f94e90726 --- /dev/null +++ b/Koin/Presentation/Shared/Chat/Models/ChatListModel.swift @@ -0,0 +1,11 @@ +// +// ChatListModel.swift +// koin +// +// Created by 홍기정 on 8/20/26. +// + +struct ChatListModel { + let dates: [String] + let messages: [[ChatMessageRowModel]] +} diff --git a/Koin/Presentation/Shared/Chat/Models/ChatMessageRowModel.swift b/Koin/Presentation/Shared/Chat/Models/ChatMessageRowModel.swift new file mode 100644 index 000000000..ed35aaf3e --- /dev/null +++ b/Koin/Presentation/Shared/Chat/Models/ChatMessageRowModel.swift @@ -0,0 +1,28 @@ +// +// ChatMessageRowModel.swift +// koin +// +// Created by 홍기정 on 8/20/26. +// + +import UIKit + +struct ChatMessageRowModel { + let alignment: ChatMessageAlignment + let content: ChatMessageContent + let senderNickname: String + let timeText: String + let showsProfile: Bool + let isLeftUser: Bool + let profileImage: UIImage? +} + +enum ChatMessageAlignment { + case left + case right +} + +enum ChatMessageContent { + case text(String) + case image(String) +} diff --git a/Koin/Presentation/Shared/Chat/Views/ChatInputView.swift b/Koin/Presentation/Shared/Chat/Views/ChatInputView.swift new file mode 100644 index 000000000..471959cf6 --- /dev/null +++ b/Koin/Presentation/Shared/Chat/Views/ChatInputView.swift @@ -0,0 +1,151 @@ +// +// ChatInputView.swift +// koin +// +// Created by 홍기정 on 8/20/26. +// + +import UIKit +import Combine +import SnapKit +import Then + +final class ChatInputView: UIView { + + // MARK: - Properties + let messageSendPublisher = PassthroughSubject() + let imageSendTappedPublisher = PassthroughSubject() + private let textViewPlaceHolder = "메시지 보내기" + + // MARK: - UI Components + private let sendImageButton = UIButton() + private let messageTextView = UITextView() + private let sendMessageButton = UIButton() + + // MARK: - Initializer + override init(frame: CGRect) { + super.init(frame: frame) + configureView() + } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - UITextViewDelegate + +extension ChatInputView: UITextViewDelegate { + func textViewDidBeginEditing(_ textView: UITextView) { + if textView.textColor == UIColor.appColor(.neutral500) { + textView.text = "" + textView.textColor = UIColor.appColor(.neutral800) + } + } + + func textViewDidEndEditing(_ textView: UITextView) { + if textView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + textView.text = textViewPlaceHolder + textView.textColor = UIColor.appColor(.neutral500) + } + } +} + +// MARK: - Actions + +private extension ChatInputView { + @objc private func sendImageButtonTapped() { + imageSendTappedPublisher.send() + } + + @objc private func sendMessageButtonTapped() { + guard messageTextView.textColor == UIColor.appColor(.neutral800) else { + return + } + + let text = messageTextView.text.trimmingCharacters(in: .whitespacesAndNewlines) + guard !text.isEmpty else { return } + + messageSendPublisher.send(text) + messageTextView.text = "" + } +} + +// MARK: - Configure + +private extension ChatInputView { + private func configureView() { + setAddTargets() + setUpStyles() + setUpLayouts() + setUpConstraints() + } + + private func setAddTargets() { + sendImageButton.addTarget(self, action: #selector(sendImageButtonTapped), for: .touchUpInside) + sendMessageButton.addTarget(self, action: #selector(sendMessageButtonTapped), for: .touchUpInside) + } + + private func setUpStyles() { + backgroundColor = UIColor.appColor(.neutral100) + + sendImageButton.do { + $0.setImage(UIImage.appImage(asset: .callVanSendImage), for: .normal) + $0.backgroundColor = UIColor.appColor(.neutral0) + $0.layer.cornerRadius = 12 + $0.clipsToBounds = true + } + + messageTextView.do { + let font = UIFont.appFont(.pretendardRegular, size: 12) + let height: CGFloat = 32 + let topBottomInset = (height - font.lineHeight) / 2 + + $0.delegate = self + $0.isScrollEnabled = false + $0.font = font + $0.backgroundColor = UIColor.appColor(.neutral0) + $0.textContainerInset = UIEdgeInsets( + top: topBottomInset, + left: 16, + bottom: topBottomInset, + right: 16 + ) + $0.layer.cornerRadius = 12 + $0.text = textViewPlaceHolder + $0.textColor = UIColor.appColor(.neutral500) + } + + sendMessageButton.do { + $0.setImage(UIImage.appImage(asset: .callVanSendMessage), for: .normal) + $0.layer.cornerRadius = 12 + $0.clipsToBounds = true + } + } + + private func setUpLayouts() { + [sendImageButton, messageTextView, sendMessageButton].forEach { + addSubview($0) + } + } + + private func setUpConstraints() { + sendImageButton.snp.makeConstraints { + $0.size.equalTo(32) + $0.top.equalToSuperview().offset(8) + $0.leading.equalToSuperview().offset(24) + } + + sendMessageButton.snp.makeConstraints { + $0.size.equalTo(32) + $0.top.equalToSuperview().offset(8) + $0.trailing.equalToSuperview().offset(-24) + } + + messageTextView.snp.makeConstraints { + $0.top.bottom.equalToSuperview().inset(8) + $0.leading.equalTo(sendImageButton.snp.trailing).offset(8) + $0.trailing.equalTo(sendMessageButton.snp.leading).offset(-8) + $0.bottom.greaterThanOrEqualTo(sendImageButton) + } + } +} diff --git a/Koin/Presentation/Shared/Chat/Views/ChatListView.swift b/Koin/Presentation/Shared/Chat/Views/ChatListView.swift new file mode 100644 index 000000000..e733a1506 --- /dev/null +++ b/Koin/Presentation/Shared/Chat/Views/ChatListView.swift @@ -0,0 +1,117 @@ +// +// ChatListView.swift +// koin +// +// Created by 홍기정 on 8/20/26. +// + +import UIKit +import Combine +import SnapKit +import Then + +final class ChatListView: UIView { + + // MARK: - Properties + let messageSendPublisher = PassthroughSubject() + let imageSendTappedPublisher = PassthroughSubject() + let imageTappedPublisher = PassthroughSubject() + private var subscriptions = Set() + + // MARK: - UI Components + private let tableView = ChatTableView() + private let chatInputView = ChatInputView() + + // MARK: - Initializer + override init(frame: CGRect) { + super.init(frame: frame) + configureView() + bind() + } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Public + func update(model: ChatListModel) { + tableView.configure(model: model) + } +} + +// MARK: - Bind + +private extension ChatListView { + private func bind() { + tableView.imageTappedPublisher + .sink { [weak self] imageUrl in + self?.imageTappedPublisher.send(imageUrl) + } + .store(in: &subscriptions) + + chatInputView.messageSendPublisher + .sink { [weak self] message in + self?.messageSendPublisher.send(message) + } + .store(in: &subscriptions) + + chatInputView.imageSendTappedPublisher + .sink { [weak self] in + self?.imageSendTappedPublisher.send() + } + .store(in: &subscriptions) + } +} + +// MARK: - Actions + +private extension ChatListView { + private func setGesture() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapAround)) + tapGesture.cancelsTouchesInView = false + tableView.addGestureRecognizer(tapGesture) + } + + @objc private func didTapAround() { + endEditing(true) + } +} + +// MARK: - Configure + +private extension ChatListView { + private func configureView() { + setGesture() + setUpStyles() + setUpLayouts() + setUpConstraints() + } + + private func setUpStyles() { + backgroundColor = UIColor.appColor(.neutral100) + + tableView.do { + $0.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi)) + $0.backgroundColor = .white + $0.separatorStyle = .none + $0.showsVerticalScrollIndicator = false + } + } + + private func setUpLayouts() { + [tableView, chatInputView].forEach { + addSubview($0) + } + } + + private func setUpConstraints() { + tableView.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.bottom.equalTo(chatInputView.snp.top) + } + + chatInputView.snp.makeConstraints { + $0.leading.trailing.equalToSuperview() + $0.bottom.equalTo(keyboardLayoutGuide.snp.top) + } + } +} diff --git a/Koin/Presentation/CallVan/CallVanChat/Subviews/CallVanChatTableView/CallVanChatDateHeaderView.swift b/Koin/Presentation/Shared/Chat/Views/ChatTableView/ChatDateHeaderView.swift similarity index 91% rename from Koin/Presentation/CallVan/CallVanChat/Subviews/CallVanChatTableView/CallVanChatDateHeaderView.swift rename to Koin/Presentation/Shared/Chat/Views/ChatTableView/ChatDateHeaderView.swift index b828c0476..d4e449d85 100644 --- a/Koin/Presentation/CallVan/CallVanChat/Subviews/CallVanChatTableView/CallVanChatDateHeaderView.swift +++ b/Koin/Presentation/Shared/Chat/Views/ChatTableView/ChatDateHeaderView.swift @@ -1,5 +1,5 @@ // -// CallVanChatDateHeaderView.swift +// ChatDateHeaderView.swift // koin // // Created by 홍기정 on 3/9/26. @@ -9,7 +9,7 @@ import UIKit import SnapKit import Then -final class CallVanChatDateHeaderView: UITableViewHeaderFooterView { +final class ChatDateHeaderView: UITableViewHeaderFooterView { // MARK: - UI Components private let dateView = UIView() @@ -30,7 +30,7 @@ final class CallVanChatDateHeaderView: UITableViewHeaderFooterView { } } -extension CallVanChatDateHeaderView { +extension ChatDateHeaderView { private func configureView() { setUpStyles() diff --git a/Koin/Presentation/CallVan/CallVanChat/Subviews/CallVanChatTableView/CallVanChatLeftCell.swift b/Koin/Presentation/Shared/Chat/Views/ChatTableView/ChatLeftCell.swift similarity index 89% rename from Koin/Presentation/CallVan/CallVanChat/Subviews/CallVanChatTableView/CallVanChatLeftCell.swift rename to Koin/Presentation/Shared/Chat/Views/ChatTableView/ChatLeftCell.swift index 8b761ae15..5a42889c5 100644 --- a/Koin/Presentation/CallVan/CallVanChat/Subviews/CallVanChatTableView/CallVanChatLeftCell.swift +++ b/Koin/Presentation/Shared/Chat/Views/ChatTableView/ChatLeftCell.swift @@ -1,5 +1,5 @@ // -// CallVanChatLeftCell.swift +// ChatLeftCell.swift // koin // // Created by 홍기정 on 3/9/26. @@ -10,7 +10,7 @@ import Combine import SnapKit import Then -final class CallVanChatLeftCell: UITableViewCell { +final class ChatLeftCell: UITableViewCell { // MARK: - Properties let imageTappedPublisher = PassthroughSubject() @@ -55,35 +55,36 @@ final class CallVanChatLeftCell: UITableViewCell { } // MARK: - Public - func configure(message: CallVanChatMessage) { + func configure(message: ChatMessageRowModel) { // MARK: 프로필 - profileWrapperView.isHidden = !message.showProfile + profileWrapperView.isHidden = !message.showsProfile profileImageView.image = message.profileImage nicknameLabel.text = message.senderNickname leftUserLabel.isHidden = !message.isLeftUser // MARK: 이미지 - messageImageWrapperView.isHidden = !message.isImage - if message.isImage { - imageUrl = message.content - messageImageView.loadImageWithSpinner(from: message.content) - messageImageTimeLabel.text = message.time - } - - // MARK: 텍스트 - messageTextWrapperView.isHidden = message.isImage - if !message.isImage { + switch message.content { + case .image(let imageUrl): + messageImageWrapperView.isHidden = false + messageTextWrapperView.isHidden = true + self.imageUrl = imageUrl + messageImageView.loadImageWithSpinner(from: imageUrl) + messageImageTimeLabel.text = message.timeText + case .text(let text): + messageImageWrapperView.isHidden = true + messageTextWrapperView.isHidden = false + imageUrl = nil messageTextLabel.attributedText = NSAttributedString( - string: message.content, + string: text, attributes: messageTextLabelAttributes ) - messageTextTimeLabel.text = message.time + messageTextTimeLabel.text = message.timeText } } } -extension CallVanChatLeftCell { +extension ChatLeftCell { override func prepareForReuse() { super.prepareForReuse() messageImageView.image = nil @@ -106,7 +107,7 @@ extension CallVanChatLeftCell { } } -extension CallVanChatLeftCell { +extension ChatLeftCell { private func configureView() { setUpStyles() diff --git a/Koin/Presentation/CallVan/CallVanChat/Subviews/CallVanChatTableView/CallVanChatRightCell.swift b/Koin/Presentation/Shared/Chat/Views/ChatTableView/ChatRightCell.swift similarity index 87% rename from Koin/Presentation/CallVan/CallVanChat/Subviews/CallVanChatTableView/CallVanChatRightCell.swift rename to Koin/Presentation/Shared/Chat/Views/ChatTableView/ChatRightCell.swift index 0d52874ce..71a0bb4e0 100644 --- a/Koin/Presentation/CallVan/CallVanChat/Subviews/CallVanChatTableView/CallVanChatRightCell.swift +++ b/Koin/Presentation/Shared/Chat/Views/ChatTableView/ChatRightCell.swift @@ -1,5 +1,5 @@ // -// CallVanChatRightCell.swift +// ChatRightCell.swift // koin // // Created by 홍기정 on 3/9/26. @@ -10,7 +10,7 @@ import Combine import SnapKit import Then -final class CallVanChatRightCell: UITableViewCell { +final class ChatRightCell: UITableViewCell { // MARK: - Properties let imageTappedPublisher = PassthroughSubject() @@ -52,32 +52,33 @@ final class CallVanChatRightCell: UITableViewCell { } // MARK: - Public - func configure(message: CallVanChatMessage) { + func configure(message: ChatMessageRowModel) { // MARK: 프로필 - profileWrapperView.isHidden = !message.showProfile + profileWrapperView.isHidden = !message.showsProfile // MARK: 이미지 - messageImageWrapperView.isHidden = !message.isImage - if message.isImage { - imageUrl = message.content - messageImageView.loadImageWithSpinner(from: message.content) - messageImageTimeLabel.text = message.time - } - - // MARK: 텍스트 - messageTextWrapperView.isHidden = message.isImage - if !message.isImage { + switch message.content { + case .image(let imageUrl): + messageImageWrapperView.isHidden = false + messageTextWrapperView.isHidden = true + self.imageUrl = imageUrl + messageImageView.loadImageWithSpinner(from: imageUrl) + messageImageTimeLabel.text = message.timeText + case .text(let text): + messageImageWrapperView.isHidden = true + messageTextWrapperView.isHidden = false + imageUrl = nil messageTextLabel.attributedText = NSAttributedString( - string: message.content, + string: text, attributes: messageTextLabelAttributes ) - messageTextTimeLabel.text = message.time + messageTextTimeLabel.text = message.timeText } } } -extension CallVanChatRightCell { +extension ChatRightCell { override func prepareForReuse() { super.prepareForReuse() messageImageView.image = nil @@ -100,7 +101,7 @@ extension CallVanChatRightCell { } } -extension CallVanChatRightCell { +extension ChatRightCell { private func configureView() { setUpStyles() diff --git a/Koin/Presentation/CallVan/CallVanChat/Subviews/CallVanChatTableView/CallVanChatTableView.swift b/Koin/Presentation/Shared/Chat/Views/ChatTableView/ChatTableView.swift similarity index 67% rename from Koin/Presentation/CallVan/CallVanChat/Subviews/CallVanChatTableView/CallVanChatTableView.swift rename to Koin/Presentation/Shared/Chat/Views/ChatTableView/ChatTableView.swift index e68f5c8df..82686d8d0 100644 --- a/Koin/Presentation/CallVan/CallVanChat/Subviews/CallVanChatTableView/CallVanChatTableView.swift +++ b/Koin/Presentation/Shared/Chat/Views/ChatTableView/ChatTableView.swift @@ -1,5 +1,5 @@ // -// CallVanChatTableView.swift +// ChatTableView.swift // koin // // Created by 홍기정 on 3/9/26. @@ -9,12 +9,12 @@ import UIKit import Combine import Then -final class CallVanChatTableView: UITableView { +final class ChatTableView: UITableView { // MARK: - Properties let imageTappedPublisher = PassthroughSubject() private var dates: [String] = [] - private var messages: [[CallVanChatMessage]] = [] + private var messages: [[ChatMessageRowModel]] = [] // MARK: - Initializer init() { @@ -26,14 +26,14 @@ final class CallVanChatTableView: UITableView { } // MARK: - Public - func configure(callVanChat: CallVanChat) { - self.dates = callVanChat.dates - self.messages = callVanChat.messages + func configure(model: ChatListModel) { + self.dates = model.dates + self.messages = model.messages reloadData() } } -extension CallVanChatTableView { +extension ChatTableView { private func commonInit() { allowsSelection = false @@ -45,16 +45,16 @@ extension CallVanChatTableView { keyboardDismissMode = .interactiveWithAccessory delegate = self dataSource = self - register(CallVanChatLeftCell.self, forCellReuseIdentifier: CallVanChatLeftCell.identifier) - register(CallVanChatRightCell.self, forCellReuseIdentifier: CallVanChatRightCell.identifier) - register(CallVanChatDateHeaderView.self, forHeaderFooterViewReuseIdentifier: CallVanChatDateHeaderView.identifier) + register(ChatLeftCell.self, forCellReuseIdentifier: ChatLeftCell.identifier) + register(ChatRightCell.self, forCellReuseIdentifier: ChatRightCell.identifier) + register(ChatDateHeaderView.self, forHeaderFooterViewReuseIdentifier: ChatDateHeaderView.identifier) } } -extension CallVanChatTableView: UITableViewDelegate { +extension ChatTableView: UITableViewDelegate { func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { - guard let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: CallVanChatDateHeaderView.identifier) as? CallVanChatDateHeaderView else { + guard let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: ChatDateHeaderView.identifier) as? ChatDateHeaderView else { return nil } headerView.configure(date: dates[section]) @@ -62,7 +62,7 @@ extension CallVanChatTableView: UITableViewDelegate { } } -extension CallVanChatTableView: UITableViewDataSource { +extension ChatTableView: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { return dates.count @@ -75,15 +75,16 @@ extension CallVanChatTableView: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let message = messages[indexPath.section][indexPath.row] - if message.isMine { - guard let cell = tableView.dequeueReusableCell(withIdentifier: CallVanChatRightCell.identifier, for: indexPath) as? CallVanChatRightCell else { + switch message.alignment { + case .right: + guard let cell = tableView.dequeueReusableCell(withIdentifier: ChatRightCell.identifier, for: indexPath) as? ChatRightCell else { return UITableViewCell() } cell.configure(message: message) bind(cell) return cell - } else { - guard let cell = tableView.dequeueReusableCell(withIdentifier: CallVanChatLeftCell.identifier, for: indexPath) as? CallVanChatLeftCell else { + case .left: + guard let cell = tableView.dequeueReusableCell(withIdentifier: ChatLeftCell.identifier, for: indexPath) as? ChatLeftCell else { return UITableViewCell() } cell.configure(message: message) @@ -92,13 +93,13 @@ extension CallVanChatTableView: UITableViewDataSource { } } - private func bind(_ cell: CallVanChatLeftCell) { + private func bind(_ cell: ChatLeftCell) { cell.imageTappedPublisher.sink { [weak self] imageUrl in self?.imageTappedPublisher.send(imageUrl) }.store(in: &cell.subscriptions) } - private func bind(_ cell: CallVanChatRightCell) { + private func bind(_ cell: ChatRightCell) { cell.imageTappedPublisher.sink { [weak self] imageUrl in self?.imageTappedPublisher.send(imageUrl) }.store(in: &cell.subscriptions) diff --git a/koin.xcodeproj/project.pbxproj b/koin.xcodeproj/project.pbxproj index 6731f5bfc..f2fa87537 100644 --- a/koin.xcodeproj/project.pbxproj +++ b/koin.xcodeproj/project.pbxproj @@ -405,10 +405,10 @@ 7CC42B892F5D85F700940CE1 /* CallVanChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC42B882F5D85F700940CE1 /* CallVanChat.swift */; }; 7CC42B8F2F5DCE7400940CE1 /* CallVanChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC42B8E2F5DCE7400940CE1 /* CallVanChatViewController.swift */; }; 7CC42B912F5DCE7D00940CE1 /* CallVanChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC42B902F5DCE7D00940CE1 /* CallVanChatViewModel.swift */; }; - 7CC42B942F5DCEA600940CE1 /* CallVanChatTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC42B932F5DCEA600940CE1 /* CallVanChatTableView.swift */; }; - 7CC42B962F5DCEB300940CE1 /* CallVanChatDateHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC42B952F5DCEB300940CE1 /* CallVanChatDateHeaderView.swift */; }; - 7CC42B982F5E812F00940CE1 /* CallVanChatLeftCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC42B972F5E812F00940CE1 /* CallVanChatLeftCell.swift */; }; - 7CC42B9C2F5E834E00940CE1 /* CallVanChatRightCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC42B9B2F5E834E00940CE1 /* CallVanChatRightCell.swift */; }; + 7CC42B942F5DCEA600940CE1 /* ChatTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC42B932F5DCEA600940CE1 /* ChatTableView.swift */; }; + 7CC42B962F5DCEB300940CE1 /* ChatDateHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC42B952F5DCEB300940CE1 /* ChatDateHeaderView.swift */; }; + 7CC42B982F5E812F00940CE1 /* ChatLeftCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC42B972F5E812F00940CE1 /* ChatLeftCell.swift */; }; + 7CC42B9C2F5E834E00940CE1 /* ChatRightCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC42B9B2F5E834E00940CE1 /* ChatRightCell.swift */; }; 7CCB1E4B2F29D87C00472669 /* LostItemPostChatDetailUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCB1E4A2F29D87C00472669 /* LostItemPostChatDetailUseCase.swift */; }; 7CCB1E4E2F29DB3200472669 /* LostItemPostChatDetailRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CCB1E4D2F29DB3200472669 /* LostItemPostChatDetailRequest.swift */; }; 7CD411EA2FEB92280079467B /* HomeTabBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CD411E92FEB92280079467B /* HomeTabBarItem.swift */; }; @@ -766,6 +766,11 @@ D19A00013000000000000002 /* NotificationRowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19A00013000000000000001 /* NotificationRowModel.swift */; }; D19A00013000000000000004 /* NotificationListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19A00013000000000000003 /* NotificationListView.swift */; }; D19A00013000000000000006 /* NotificationRowModel+NotificationHistoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19A00013000000000000005 /* NotificationRowModel+NotificationHistoryItem.swift */; }; + D19A10013000000000000002 /* ChatListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19A10013000000000000001 /* ChatListModel.swift */; }; + D19A10013000000000000004 /* ChatMessageRowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19A10013000000000000003 /* ChatMessageRowModel.swift */; }; + D19A10013000000000000006 /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19A10013000000000000005 /* ChatListView.swift */; }; + D19A10013000000000000008 /* ChatInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19A10013000000000000007 /* ChatInputView.swift */; }; + D19A1001300000000000000A /* ChatListModel+CallVanChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19A10013000000000000009 /* ChatListModel+CallVanChat.swift */; }; D2009F762C17270F00211D1B /* CalendarDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2009F752C17270F00211D1B /* CalendarDate.swift */; }; D2009F7F2C1784DA00211D1B /* MockAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2009F7E2C1784DA00211D1B /* MockAnalyticsService.swift */; }; D2078BF62BC4CCEB00A39861 /* EventDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2078BF52BC4CCEB00A39861 /* EventDto.swift */; }; @@ -1410,10 +1415,10 @@ 7CC42B882F5D85F700940CE1 /* CallVanChat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVanChat.swift; sourceTree = ""; }; 7CC42B8E2F5DCE7400940CE1 /* CallVanChatViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVanChatViewController.swift; sourceTree = ""; }; 7CC42B902F5DCE7D00940CE1 /* CallVanChatViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVanChatViewModel.swift; sourceTree = ""; }; - 7CC42B932F5DCEA600940CE1 /* CallVanChatTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVanChatTableView.swift; sourceTree = ""; }; - 7CC42B952F5DCEB300940CE1 /* CallVanChatDateHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVanChatDateHeaderView.swift; sourceTree = ""; }; - 7CC42B972F5E812F00940CE1 /* CallVanChatLeftCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVanChatLeftCell.swift; sourceTree = ""; }; - 7CC42B9B2F5E834E00940CE1 /* CallVanChatRightCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVanChatRightCell.swift; sourceTree = ""; }; + 7CC42B932F5DCEA600940CE1 /* ChatTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTableView.swift; sourceTree = ""; }; + 7CC42B952F5DCEB300940CE1 /* ChatDateHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatDateHeaderView.swift; sourceTree = ""; }; + 7CC42B972F5E812F00940CE1 /* ChatLeftCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatLeftCell.swift; sourceTree = ""; }; + 7CC42B9B2F5E834E00940CE1 /* ChatRightCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRightCell.swift; sourceTree = ""; }; 7CCB1E4A2F29D87C00472669 /* LostItemPostChatDetailUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemPostChatDetailUseCase.swift; sourceTree = ""; }; 7CCB1E4D2F29DB3200472669 /* LostItemPostChatDetailRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LostItemPostChatDetailRequest.swift; sourceTree = ""; }; 7CD411E92FEB92280079467B /* HomeTabBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTabBarItem.swift; sourceTree = ""; }; @@ -1774,6 +1779,11 @@ D19A00013000000000000001 /* NotificationRowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRowModel.swift; sourceTree = ""; }; D19A00013000000000000003 /* NotificationListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationListView.swift; sourceTree = ""; }; D19A00013000000000000005 /* NotificationRowModel+NotificationHistoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationRowModel+NotificationHistoryItem.swift"; sourceTree = ""; }; + D19A10013000000000000001 /* ChatListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListModel.swift; sourceTree = ""; }; + D19A10013000000000000003 /* ChatMessageRowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageRowModel.swift; sourceTree = ""; }; + D19A10013000000000000005 /* ChatListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListView.swift; sourceTree = ""; }; + D19A10013000000000000007 /* ChatInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInputView.swift; sourceTree = ""; }; + D19A10013000000000000009 /* ChatListModel+CallVanChat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatListModel+CallVanChat.swift"; sourceTree = ""; }; D2009F752C17270F00211D1B /* CalendarDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarDate.swift; sourceTree = ""; }; D2009F7E2C1784DA00211D1B /* MockAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAnalyticsService.swift; sourceTree = ""; }; D2078BF52BC4CCEB00A39861 /* EventDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDto.swift; sourceTree = ""; }; @@ -3253,30 +3263,19 @@ 7CC42B8A2F5DCE4800940CE1 /* CallVanChat */ = { isa = PBXGroup; children = ( - 7CC42B8B2F5DCE5700940CE1 /* Subviews */, + 7CC42B8B2F5DCE5700940CE1 /* Support */, 7CC42B8E2F5DCE7400940CE1 /* CallVanChatViewController.swift */, 7CC42B902F5DCE7D00940CE1 /* CallVanChatViewModel.swift */, ); path = CallVanChat; sourceTree = ""; }; - 7CC42B8B2F5DCE5700940CE1 /* Subviews */ = { + 7CC42B8B2F5DCE5700940CE1 /* Support */ = { isa = PBXGroup; children = ( - 7CC42B922F5DCE9800940CE1 /* CallVanChatTableView */, + D19A10013000000000000009 /* ChatListModel+CallVanChat.swift */, ); - path = Subviews; - sourceTree = ""; - }; - 7CC42B922F5DCE9800940CE1 /* CallVanChatTableView */ = { - isa = PBXGroup; - children = ( - 7CC42B932F5DCEA600940CE1 /* CallVanChatTableView.swift */, - 7CC42B952F5DCEB300940CE1 /* CallVanChatDateHeaderView.swift */, - 7CC42B972F5E812F00940CE1 /* CallVanChatLeftCell.swift */, - 7CC42B9B2F5E834E00940CE1 /* CallVanChatRightCell.swift */, - ); - path = CallVanChatTableView; + path = Support; sourceTree = ""; }; 7CCB1E4C2F29DB2500472669 /* LostItemChat */ = { @@ -4665,6 +4664,7 @@ D19A00013000000000000007 /* Shared */ = { isa = PBXGroup; children = ( + D19A1001300000000000000C /* Chat */, D19A00013000000000000008 /* Notification */, ); path = Shared; @@ -4695,6 +4695,45 @@ path = Support; sourceTree = ""; }; + D19A1001300000000000000C /* Chat */ = { + isa = PBXGroup; + children = ( + D19A1001300000000000000D /* Models */, + D19A1001300000000000000E /* Views */, + ); + path = Chat; + sourceTree = ""; + }; + D19A1001300000000000000D /* Models */ = { + isa = PBXGroup; + children = ( + D19A10013000000000000001 /* ChatListModel.swift */, + D19A10013000000000000003 /* ChatMessageRowModel.swift */, + ); + path = Models; + sourceTree = ""; + }; + D19A1001300000000000000E /* Views */ = { + isa = PBXGroup; + children = ( + D19A10013000000000000005 /* ChatListView.swift */, + D19A10013000000000000007 /* ChatInputView.swift */, + 7CC42B922F5DCE9800940CE1 /* ChatTableView */, + ); + path = Views; + sourceTree = ""; + }; + 7CC42B922F5DCE9800940CE1 /* ChatTableView */ = { + isa = PBXGroup; + children = ( + 7CC42B932F5DCEA600940CE1 /* ChatTableView.swift */, + 7CC42B952F5DCEB300940CE1 /* ChatDateHeaderView.swift */, + 7CC42B972F5E812F00940CE1 /* ChatLeftCell.swift */, + 7CC42B9B2F5E834E00940CE1 /* ChatRightCell.swift */, + ); + path = ChatTableView; + sourceTree = ""; + }; D2009F7D2C1784C000211D1B /* MockService */ = { isa = PBXGroup; children = ( @@ -5800,7 +5839,7 @@ D874DB4D2DBF1A6C0098EED0 /* SendVerificationCodeUsecase.swift in Sources */, 7CC42B622F59D0EF00940CE1 /* CallVanPostPlaceView.swift in Sources */, D27DD0E42BA609F30081FD36 /* ShopDataDto.swift in Sources */, - 7CC42B9C2F5E834E00940CE1 /* CallVanChatRightCell.swift in Sources */, + 7CC42B9C2F5E834E00940CE1 /* ChatRightCell.swift in Sources */, D20AD6EB2E028F950067760A /* FindIdSmsRequest.swift in Sources */, D27DD0CB2BA608310081FD36 /* ShopCategoryDto.swift in Sources */, 7C6790B32FCC5B2A0045E163 /* TimetableColorAsset.swift in Sources */, @@ -5836,7 +5875,7 @@ D27DD0CC2BA608310081FD36 /* ShopsDto.swift in Sources */, 7C61D3632F242D8D005FAC3A /* AddLostItemHeaderView.swift in Sources */, D874DB492DBE31530098EED0 /* CheckDuplicatedPhoneNumberUseCase.swift in Sources */, - 7CC42B982F5E812F00940CE1 /* CallVanChatLeftCell.swift in Sources */, + 7CC42B982F5E812F00940CE1 /* ChatLeftCell.swift in Sources */, 83DD0DE62C57406600278ABD /* GetBusFiltersUseCase.swift in Sources */, D22C54732C6A2A73000826DA /* FetchMyReviewUseCase.swift in Sources */, D29F21D52C4E1A8300994554 /* RegisterUseCase.swift in Sources */, @@ -6183,7 +6222,7 @@ 7C8A00012FD000000011D338 /* HomeViewModel.swift in Sources */, 7CED917A2EF2D1F900457128 /* ShopSummaryTableView.swift in Sources */, 7C61D34B2F220EB3005FAC3A /* EditLostItemFoundPlaceView.swift in Sources */, - 7CC42B942F5DCEA600940CE1 /* CallVanChatTableView.swift in Sources */, + 7CC42B942F5DCEA600940CE1 /* ChatTableView.swift in Sources */, 7CED917D2EF2D1F900457128 /* ReviewImageUploadCollectionView.swift in Sources */, 7CED917F2EF2D1F900457128 /* NoticeListViewController.swift in Sources */, 7CED91822EF2D1F900457128 /* DeliveryTipsCollectionView.swift in Sources */, @@ -6352,7 +6391,7 @@ 7C6403FE300F36F200A9589F /* DepartmentAPI.swift in Sources */, 7CA60D632F66197C007C08C1 /* CallVanRepository.swift in Sources */, 7C0C188F2F67F8BA001781B3 /* CallVanDataDto.swift in Sources */, - 7CC42B962F5DCEB300940CE1 /* CallVanChatDateHeaderView.swift in Sources */, + 7CC42B962F5DCEB300940CE1 /* ChatDateHeaderView.swift in Sources */, 7CED91DD2EF2D1F900457128 /* ShopDetailTableViewNameCell.swift in Sources */, 7C8A943B2FD424A600DEA6F5 /* WeatherDto.swift in Sources */, 7CED91DF2EF2D1F900457128 /* NotiViewController.swift in Sources */, @@ -6485,6 +6524,11 @@ D19A00013000000000000002 /* NotificationRowModel.swift in Sources */, D19A00013000000000000004 /* NotificationListView.swift in Sources */, D19A00013000000000000006 /* NotificationRowModel+NotificationHistoryItem.swift in Sources */, + D19A10013000000000000002 /* ChatListModel.swift in Sources */, + D19A10013000000000000004 /* ChatMessageRowModel.swift in Sources */, + D19A10013000000000000006 /* ChatListView.swift in Sources */, + D19A10013000000000000008 /* ChatInputView.swift in Sources */, + D19A1001300000000000000A /* ChatListModel+CallVanChat.swift in Sources */, 7C8BFCE52FD04B8000963679 /* CategoryModels.swift in Sources */, 7C8BFCF12FD04B800963679 /* HomeCounts.swift in Sources */, D2908D262BB8D11A0008F908 /* SemesterDto.swift in Sources */,