Skip to content
72 changes: 72 additions & 0 deletions Koin/Apps/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
name: NSNotification.Name("Notification Sent"),
object: nil
)
// 현재 보고 있는 채팅방의 푸시는 묵살
if await shouldSuppressForegroundNotification(userInfo: notification.request.content.userInfo) {
return []
}
// 푸시 알림 옵션 반환
return [[.banner, .list, .sound]]
}
Expand All @@ -98,3 +102,71 @@ extension AppDelegate: MessagingDelegate {
KeychainWorker.shared.create(key: .fcm, token: fcmToken)
}
}

// MARK: - Foreground 채팅 푸시 묵살 여부
/// 채팅 중 푸시알림이 계속 도착하는 것을 방지한다. 채팅 수정 예정이므로 임시구현한다.
extension AppDelegate {
private func shouldSuppressForegroundNotification(userInfo: [AnyHashable: Any]) async -> Bool {
guard let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate,
let topViewController = sceneDelegate.navigationController?.topViewController else {
return false
}

// 팀원모집 채팅
if let type = userInfo["type"] as? String,
RecruitNotificationType(rawValue: type) == .newChatMessage {
guard let recruitChatViewController = topViewController as? RecruitChatViewController,
let notificationId = Int(userInfo["notification_id"] as? String) else {
return false
}
// 푸시에 채팅방 정보가 없으므로 알림 목록에서 notificationId 로 조회한다.
let fetchRecruitNotificationListUseCase = DefaultFetchRecruitNotificationListUseCase(
repository: DefaultRecruitRepository(service: DefaultRecruitService())
)
guard let notificationList = try? await fetchRecruitNotificationListUseCase.execute(page: 1),
let notification = notificationList.notifications.first(where: { $0.id == notificationId }),
sceneDelegate.navigationController?.topViewController === recruitChatViewController else {
return false
}
// RecruitNotificationListViewController 의 채팅방 진입 규칙과 동일하게 비교한다.
switch recruitChatViewController.roomSource {
case let .direct(recruitmentId, applicationId):
return notification.recruitmentId == recruitmentId
&& notification.applicationId == applicationId
case let .team(recruitmentId, chatRoomId):
return notification.recruitmentId == recruitmentId
&& notification.applicationId == nil
&& notification.chatRoomId == chatRoomId
}
}

// 분실물 채팅, 콜밴 채팅
guard let aps = userInfo["aps"] as? [String: AnyObject],
let category = aps["category"] as? String,
let appPath = AppPath(rawValue: category),
let schemeUri = userInfo["schemeUri"] as? String,
let queryItems = URLComponents(string: schemeUri)?.queryItems else {
return false
}
let query = queryItems.reduce(into: [String: String]()) { result, item in
result[item.name] = item.value
}

switch appPath {
case .chat:
guard let lostItemChatViewController = topViewController as? LostItemChatViewController,
let chatRoomId = Int(query["chatRoomId"]) else {
return false
}
return lostItemChatViewController.chatRoomId == chatRoomId
case .callvanChat:
guard let callVanChatViewController = topViewController as? CallVanChatViewController,
let postId = Int(query["postId"]) else {
return false
}
return callVanChatViewController.postId == postId
default:
return false
}
}
}
1 change: 1 addition & 0 deletions Koin/Core/View/UIKit/Chat/Models/ChatMessageRowModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct ChatMessageRowModel {
let showsProfile: Bool
let isLeftUser: Bool
let profileImage: UIImage?
let unreadCount: String?
}

enum ChatMessageAlignment {
Expand Down
26 changes: 22 additions & 4 deletions Koin/Core/View/UIKit/Chat/Views/ChatTableView/ChatLeftCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ final class ChatLeftCell: UITableViewCell {
private let messageImageWrapperView = UIView()
private let messageImageView = UIImageView()
private let messageImageTimeLabel = UILabel()
private let messageImageUnreadCountLabel = UILabel()

private let messageTextWrapperView = UIView()
private let messageTextView = UIView()
private let messageTextLabel = UILabel()
private let messageTextTimeLabel = UILabel()
private let messageTextUnreadCountLabel = UILabel()

// MARK: - Initializer
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
Expand Down Expand Up @@ -71,6 +73,7 @@ final class ChatLeftCell: UITableViewCell {
self.imageUrl = imageUrl
messageImageView.loadImageWithSpinner(from: imageUrl)
messageImageTimeLabel.text = message.timeText
messageImageUnreadCountLabel.text = message.unreadCount ?? ""
case .text(let text):
messageImageWrapperView.isHidden = true
messageTextWrapperView.isHidden = false
Expand All @@ -80,6 +83,7 @@ final class ChatLeftCell: UITableViewCell {
attributes: messageTextLabelAttributes
)
messageTextTimeLabel.text = message.timeText
messageTextUnreadCountLabel.text = message.unreadCount ?? ""
}
}
}
Expand Down Expand Up @@ -150,6 +154,10 @@ extension ChatLeftCell {
$0.font = UIFont.appFont(.pretendardRegular, size: 12)
$0.textColor = UIColor.appColor(.neutral500)
}
messageImageUnreadCountLabel.do {
$0.font = .appFont(.pretendardRegular, size: 12)
$0.textColor = .appColor(.neutral700)
}

// MARK: 텍스트
messageTextView.do {
Expand All @@ -163,16 +171,20 @@ extension ChatLeftCell {
$0.font = UIFont.appFont(.pretendardRegular, size: 12)
$0.textColor = UIColor.appColor(.neutral500)
}
messageTextUnreadCountLabel.do {
$0.font = .appFont(.pretendardRegular, size: 12)
$0.textColor = .appColor(.neutral700)
}
}

private func setUpLayouts() {
[profileImageView, nicknameLabel, leftUserLabel].forEach {
profileWrapperView.addSubview($0)
}
[messageImageView, messageImageTimeLabel].forEach {
[messageImageView, messageImageTimeLabel, messageImageUnreadCountLabel].forEach {
messageImageWrapperView.addSubview($0)
}
[messageTextView, messageTextLabel, messageTextTimeLabel].forEach {
[messageTextView, messageTextLabel, messageTextTimeLabel, messageTextUnreadCountLabel].forEach {
messageTextWrapperView.addSubview($0)
}
[profileWrapperView, messageImageWrapperView, messageTextWrapperView].forEach {
Expand Down Expand Up @@ -211,10 +223,13 @@ extension ChatLeftCell {
$0.top.leading.bottom.equalTo(messageImageWrapperView)
}
messageImageTimeLabel.snp.makeConstraints {
$0.height.equalTo(19)
$0.leading.equalTo(messageImageView.snp.trailing).offset(8)
$0.bottom.trailing.equalTo(messageImageWrapperView)
}
messageImageUnreadCountLabel.snp.makeConstraints {
$0.leading.equalTo(messageImageTimeLabel)
$0.bottom.equalTo(messageImageTimeLabel.snp.top)
}

// MARK: 텍스트
messageTextView.snp.makeConstraints {
Expand All @@ -226,9 +241,12 @@ extension ChatLeftCell {
$0.leading.trailing.equalTo(messageTextView).inset(12)
}
messageTextTimeLabel.snp.makeConstraints {
$0.height.equalTo(19)
$0.leading.equalTo(messageTextView.snp.trailing).offset(8)
$0.bottom.trailing.equalTo(messageTextWrapperView)
}
messageTextUnreadCountLabel.snp.makeConstraints {
$0.leading.equalTo(messageTextTimeLabel)
$0.bottom.equalTo(messageTextTimeLabel.snp.top)
}
}
}
26 changes: 22 additions & 4 deletions Koin/Core/View/UIKit/Chat/Views/ChatTableView/ChatRightCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ final class ChatRightCell: UITableViewCell {
private let messageImageWrapperView = UIView()
private let messageImageView = UIImageView()
private let messageImageTimeLabel = UILabel()
private let messageImageUnreadCountLabel = UILabel()

private let messageTextWrapperView = UIView()
private let messageTextView = UIView()
private let messageTextLabel = UILabel()
private let messageTextTimeLabel = UILabel()
private let messageTextUnreadCountLabel = UILabel()

// MARK: - Initializer
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
Expand All @@ -65,6 +67,7 @@ final class ChatRightCell: UITableViewCell {
self.imageUrl = imageUrl
messageImageView.loadImageWithSpinner(from: imageUrl)
messageImageTimeLabel.text = message.timeText
messageImageUnreadCountLabel.text = message.unreadCount ?? ""
case .text(let text):
messageImageWrapperView.isHidden = true
messageTextWrapperView.isHidden = false
Expand All @@ -74,6 +77,7 @@ final class ChatRightCell: UITableViewCell {
attributes: messageTextLabelAttributes
)
messageTextTimeLabel.text = message.timeText
messageTextUnreadCountLabel.text = message.unreadCount ?? ""
}
}
}
Expand Down Expand Up @@ -133,6 +137,10 @@ extension ChatRightCell {
$0.font = UIFont.appFont(.pretendardRegular, size: 12)
$0.textColor = UIColor.appColor(.neutral500)
}
messageImageUnreadCountLabel.do {
$0.font = .appFont(.pretendardRegular, size: 12)
$0.textColor = .appColor(.neutral700)
}

// MARK: 텍스트
messageTextView.do {
Expand All @@ -146,13 +154,17 @@ extension ChatRightCell {
$0.font = UIFont.appFont(.pretendardRegular, size: 12)
$0.textColor = UIColor.appColor(.neutral500)
}
messageTextUnreadCountLabel.do {
$0.font = .appFont(.pretendardRegular, size: 12)
$0.textColor = .appColor(.neutral700)
}
}

private func setUpLayouts() {
[messageImageView, messageImageTimeLabel].forEach {
[messageImageView, messageImageTimeLabel, messageImageUnreadCountLabel].forEach {
messageImageWrapperView.addSubview($0)
}
[messageTextView, messageTextLabel, messageTextTimeLabel].forEach {
[messageTextView, messageTextLabel, messageTextTimeLabel, messageTextUnreadCountLabel].forEach {
messageTextWrapperView.addSubview($0)
}
[profileWrapperView, messageImageWrapperView, messageTextWrapperView].forEach {
Expand Down Expand Up @@ -181,10 +193,13 @@ extension ChatRightCell {
$0.top.trailing.bottom.equalTo(messageImageWrapperView)
}
messageImageTimeLabel.snp.makeConstraints {
$0.height.equalTo(19)
$0.trailing.equalTo(messageImageView.snp.leading).offset(-8)
$0.bottom.leading.equalTo(messageImageWrapperView)
}
messageImageUnreadCountLabel.snp.makeConstraints {
$0.trailing.equalTo(messageImageTimeLabel)
$0.bottom.equalTo(messageImageTimeLabel.snp.top)
}

// MARK: 텍스트
messageTextView.snp.makeConstraints {
Expand All @@ -196,9 +211,12 @@ extension ChatRightCell {
$0.leading.trailing.equalTo(messageTextView).inset(12)
}
messageTextTimeLabel.snp.makeConstraints {
$0.height.equalTo(19)
$0.trailing.equalTo(messageTextView.snp.leading).offset(-8)
$0.bottom.leading.equalTo(messageTextWrapperView)
}
messageTextUnreadCountLabel.snp.makeConstraints {
$0.trailing.equalTo(messageTextTimeLabel)
$0.bottom.equalTo(messageTextTimeLabel.snp.top)
}
}
}
5 changes: 4 additions & 1 deletion Koin/Data/DTOs/Decodable/Recruit/RecruitChatDataDto.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ struct RecruitChatDataDto: Decodable {
let roomType: RecruitChatRoomTypeDto
let memberCount: Int?
let maxMemberCount: Int?
let unreadCount: Int?

enum CodingKeys: String, CodingKey {
case chatRoomId = "chat_room_id"
case roomName = "room_name"
case roomType = "room_type"
case memberCount = "member_count"
case maxMemberCount = "max_member_count"
case unreadCount = "unread_count"
}
}

Expand All @@ -35,7 +37,8 @@ extension RecruitChatDataDto {
chatRoomName: roomName,
chatRoomType: roomType.toDomain(),
currentMemberCount: memberCount,
maximumMemberCount: maxMemberCount
maximumMemberCount: maxMemberCount,
unreadCount: unreadCount
)
}
}
Expand Down
4 changes: 3 additions & 1 deletion Koin/Data/DTOs/Decodable/Recruit/RecruitChatMessageDto.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ extension Array where Element == RecruitChatMessageDto {
let showProfile = !(dto.userId == previousUserId && date == previousDate)
previousUserId = dto.userId
previousDate = date

let unreadCount = dto.unreadCount == 0 ? nil : "\(dto.unreadCount)"

return RecruitChatMessage(
messageId: dto.messageId,
Expand All @@ -63,7 +65,7 @@ extension Array where Element == RecruitChatMessageDto {
content: dto.content,
timestamp: timestamp,
isImage: dto.isImage,
unreadCount: dto.unreadCount,
unreadCount: unreadCount,
isMine: dto.userId == myUserId,
showProfile: showProfile,
profileImage: profileImages[profileIndex]
Expand Down
3 changes: 3 additions & 0 deletions Koin/Domain/Model/Recruit/BasicInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ extension BasicInfo {
let studentNumber, !studentNumber.isEmpty else {
return false
}
guard StudentNumberInput.isValid(studentNumber) else {
return false
}
return true
}
}
1 change: 1 addition & 0 deletions Koin/Domain/Model/Recruit/RecruitChatData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct RecruitChatData {
let chatRoomType: RecruitChatRoomType
let currentMemberCount: Int?
let maximumMemberCount: Int?
let unreadCount: Int?
}

enum RecruitChatRoomType: String {
Expand Down
2 changes: 1 addition & 1 deletion Koin/Domain/Model/Recruit/RecruitChatMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct RecruitChatMessage {
let content: String
let timestamp: Date
let isImage: Bool
let unreadCount: Int
let unreadCount: String?
let isMine: Bool
let showProfile: Bool
let profileImage: ImageAsset?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ final class CallVanChatViewController: UIViewController {
private let inputSubject = PassthroughSubject<CallVanChatViewModel.Input, Never>()
private let viewModel: CallVanChatViewModel
private var subscriptions: Set<AnyCancellable> = []
var postId: Int { viewModel.postId }

// MARK: - TitleView
private lazy var titleLabel = UILabel()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ extension ChatMessageRowModel {
timeText: message.time,
showsProfile: message.showProfile,
isLeftUser: message.isLeftUser,
profileImage: message.profileImage
profileImage: message.profileImage,
unreadCount: nil
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import PhotosUI
import UIKit

final class LostItemChatViewController: UIViewController, UITextViewDelegate, PHPickerViewControllerDelegate {
//

// MARK: - Properties
private let viewModel: LostItemChatViewModel
private let inputSubject: PassthroughSubject<LostItemChatViewModel.Input, Never> = .init()
private var subscriptions: Set<AnyCancellable> = []
private var messageInputBottomConstraint: NSLayoutConstraint!
private var textViewHeightConstraint: NSLayoutConstraint!
var chatRoomId: Int { viewModel.chatRoomId }

// MARK: - UI Components
private let bottomBackgroundView = UIView().then {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ extension ChatMessageRowModel {
timeText: message.displayTime,
showsProfile: message.showProfile,
isLeftUser: false,
profileImage: message.profileImage.flatMap { UIImage.appImage(asset: $0) }
profileImage: message.profileImage.flatMap { UIImage.appImage(asset: $0) },
unreadCount: message.unreadCount
)
}
}
Loading
Loading