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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Wable-iOS/Data/Mapper/NotificationMapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ enum NotificationMapper {
ActivityNotification(
id: dto.notificationID,
triggerID: dto.notificationTriggerID,
type: TriggerType.ActivityNotification(rawValue: dto.notificationTriggerType),
type: TriggerType.ActivityNotification.from(dto.notificationTriggerType),
time: dateFormatter.date(from: dto.time),
targetContentText: dto.notificationText,
userID: dto.memberID,
Expand Down
20 changes: 8 additions & 12 deletions Wable-iOS/Data/RepositoryImpl/URLPreviewRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,19 @@ import Foundation

import SwiftSoup

final class URLPreviewRepositoryImpl: URLPreviewRepository {
private let session: URLSession

init(session: URLSession = .shared) {
self.session = session
}

struct URLPreviewRepositoryImpl: URLPreviewRepository {
func fetchPreview(url: URL) -> AnyPublisher<URLPreview, WableError> {
let config = URLSessionConfiguration.ephemeral
config.timeoutIntervalForRequest = 5
config.timeoutIntervalForResource = 10

let session = URLSession(configuration: config)

return session.dataTaskPublisher(for: url)
.mapError { _ -> WableError in
return WableError.networkError
}
.tryMap { [weak self] data, response -> URLPreview in
guard let self else {
throw WableError.unknownError
}

.tryMap { data, response -> URLPreview in
guard let httpResponse = response as? HTTPURLResponse else {
throw WableError.networkError
}
Expand Down
10 changes: 10 additions & 0 deletions Wable-iOS/Domain/Enum/TriggerType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ enum TriggerType {
case popularContent = "popularContent"
case childComment = "childComment"
case childCommentLike = "childCommentLiked"
case viewitLike = "viewitLiked"

static func from(_ value: String) -> Self? {
if let result = Self(rawValue: value) {
return result
}

WableLogger.log("\(String(describing: Self.self))생성 실패: \(value)", for: .debug)
return nil
}
}

// MARK: - 투명도 낮추기에 대한 종류
Expand Down
72 changes: 32 additions & 40 deletions Wable-iOS/Domain/UseCase/Viewit/CreateViewitUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,64 +9,56 @@ import Combine
import Foundation

protocol CreateViewitUseCase {
func validate(_ urlString: String) -> Bool
func execute(urlString: String, description: String) -> AnyPublisher<Void, WableError>
func validate(_ urlString: String) -> AnyPublisher<Bool, WableError>
func execute(description: String) -> AnyPublisher<Bool, WableError>
}

final class CreateViewitUseCaseImpl: CreateViewitUseCase {
private static let urlDetector: NSDataDetector? = {
do {
return try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
} catch {
WableLogger.log("NSDataDetector 초기화 오류: \(error.localizedDescription)", for: .error)
return nil
}
}()

@Injected private var urlPreviewRepository: URLPreviewRepository
@Injected private var viewitRepository: ViewitRepository

func validate(_ urlString: String) -> Bool {
guard let detector = Self.urlDetector else {
return false
}

let range = NSRange(location: 0, length: urlString.utf16.count)
let matches = detector.matches(in: urlString, options: [], range: range)

if let match = matches.first,
match.range.length == urlString.utf16.count {
return true
}

return false
}
private var urlPreview: URLPreview?

func execute(urlString: String, description: String) -> AnyPublisher<Void, WableError> {
func validate(_ urlString: String) -> AnyPublisher<Bool, WableError> {
urlPreview = nil

let updatedURLString = checkURLScheme(urlString)

guard let url = URL(string: updatedURLString) else {
return .fail(.unknownError)
}

guard let scheme = url.scheme, !scheme.isEmpty,
let host = url.host, !host.isEmpty
else {
return .fail(.validationException)
}

return urlPreviewRepository.fetchPreview(url: url)
.flatMap { [weak self] preview -> AnyPublisher<Void, WableError> in
guard let self else {
return .fail(.unknownError)
}

return viewitRepository.createViewit(
thumbnailImageURLString: preview.imageURLString,
urlString: updatedURLString,
title: preview.title,
text: description,
siteName: preview.siteName
)
.eraseToAnyPublisher()
}
.handleEvents(receiveOutput: { [weak self] preview in
self?.urlPreview = preview
})
.map { _ in true }
.eraseToAnyPublisher()
}

func execute(description: String) -> AnyPublisher<Bool, WableError> {
guard let urlPreview else {
return .fail(.unknownError)
}

return viewitRepository.createViewit(
thumbnailImageURLString: urlPreview.imageURLString,
urlString: urlPreview.urlString,
title: urlPreview.title,
text: description,
siteName: urlPreview.siteName
)
.map { _ in true }
.eraseToAnyPublisher()
}

// MARK: - Helper Method

private func checkURLScheme(_ urlText: String) -> String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ final class CommunityViewController: UIViewController {
private let viewDidRefreshRelay = PassthroughRelay<Void>()
private let registerRelay = PassthroughRelay<Int>()
private let copyLinkRelay = PassthroughRelay<Void>()
private let checkNotificationAuthorizationRelay = PassthroughRelay<Void>()
private let cancelBag = CancelBag()
private let rootView = CommunityView()

Expand Down Expand Up @@ -148,13 +149,15 @@ private extension CommunityViewController {
let input = ViewModel.Input(
viewDidLoad: viewDidLoadRelay.eraseToAnyPublisher(),
viewDidRefresh: viewDidRefreshRelay.eraseToAnyPublisher(),
register: registerRelay.eraseToAnyPublisher()
register: registerRelay.eraseToAnyPublisher(),
checkNotificationAuthorization: checkNotificationAuthorizationRelay.eraseToAnyPublisher()
)

let output = viewModel.transform(input: input, cancelBag: cancelBag)

output.communityItems
.sink { [weak self] communityItems in
WableLogger.log("커뮤니티 아이템: \(communityItems)", for: .debug)
self?.applySnapshot(items: communityItems)
}
.store(in: cancelBag)
Expand All @@ -173,6 +176,13 @@ private extension CommunityViewController {
self?.showCompleteSheet(for: team.rawValue)
}
.store(in: cancelBag)

output.isNotificationAuthorized
.filter { !$0 }
.sink { [weak self] _ in
self?.showAlarmSettingSheet()
}
.store(in: cancelBag)
}
}

Expand Down Expand Up @@ -206,7 +216,7 @@ private extension CommunityViewController {

DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
completeSheet.dismiss(animated: true) { [weak self] in
self?.showAlarmSettingSheet()
self?.checkNotificationAuthorizationRelay.send()
}
}
}
Expand All @@ -222,8 +232,7 @@ private extension CommunityViewController {
guard let settingURL = URL(string: UIApplication.openSettingsURLString),
UIApplication.shared.canOpenURL(settingURL)
else {
WableLogger.log("설정 창을 열 수 없습니다!", for: .error)
return
return WableLogger.log("설정 창을 열 수 없습니다!", for: .error)
}
UIApplication.shared.open(settingURL)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Combine
import Foundation
import UserNotifications

final class CommunityViewModel {
private let useCase: CommunityUseCase
Expand All @@ -21,12 +22,14 @@ extension CommunityViewModel: ViewModelType {
let viewDidLoad: Driver<Void>
let viewDidRefresh: Driver<Void>
let register: Driver<Int>
let checkNotificationAuthorization: Driver<Void>
}

struct Output {
let communityItems: Driver<[CommunityItem]>
let isLoading: Driver<Bool>
let completeRegistration: Driver<LCKTeam?>
let isNotificationAuthorized: Driver<Bool>
}

func transform(input: Input, cancelBag: CancelBag) -> Output {
Expand Down Expand Up @@ -100,15 +103,21 @@ extension CommunityViewModel: ViewModelType {
.eraseToAnyPublisher()
}
.sink { updatedRate in
guard let team = registrationRelay.value.team else { return }
guard let team = registrationRelay.value.team,
let index = communityListRelay.value.firstIndex(where: { $0.team == team })
else {
WableLogger.log("팀을 찾을 수 없습니다.", for: .debug)
return
}

var community = communityListRelay.value.first { $0.team == team }
community?.registrationRate = updatedRate
communityListRelay.value[index].registrationRate = updatedRate
}
.store(in: cancelBag)

let communityItems = Publishers.CombineLatest(communityListRelay, registrationRelay)
.map { communityList, registration in
let communityItems = communityListRelay
.map { communityList in
let registration = registrationRelay.value

return communityList.map {
let isRegistered = registration.hasRegisteredTeam
? $0.team == registration.team
Expand All @@ -127,10 +136,22 @@ extension CommunityViewModel: ViewModelType {
})
.asDriver()

let isNotificationAuthorized = input.checkNotificationAuthorization
.flatMap { _ -> AnyPublisher<Bool, Never> in
Future { promise in
UNUserNotificationCenter.current().getNotificationSettings { settings in
promise(.success(settings.authorizationStatus == .authorized))
}
}
.eraseToAnyPublisher()
}
.asDriver()

return Output(
communityItems: communityItems,
isLoading: isLoadingRelay.asDriver(),
completeRegistration: completeRegistrationRelay.asDriver()
completeRegistration: completeRegistrationRelay.asDriver(),
isNotificationAuthorized: isNotificationAuthorized
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation
extension ActivityNotification {
var message: String {
guard let type else {
return ""
return "알 수 없는 메세지"
}

switch type {
Expand Down Expand Up @@ -38,6 +38,8 @@ extension ActivityNotification {
return "\(triggerUserNickname)님이 \(userNickname)님에게 대댓글을 작성했습니다."
case .childCommentLike:
return "\(triggerUserNickname)님이 \(userNickname)님의 대댓글을 좋아합니다."
case .viewitLike:
return "\(triggerUserNickname)님이 \(userNickname)님의 추천 링크를 좋아합니다."
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ final class CreateViewitViewController: UIViewController {

private let viewModel: ViewModel
private let urlTextRelay = PassthroughRelay<String>()
private let nextRelay = PassthroughRelay<Void>()
private let contentTextRelay = PassthroughRelay<String>()
private let uploadButtonRelay = PassthroughRelay<Void>()
private let backgroundTapRelay = PassthroughRelay<Void>()
private let cancelBag = CancelBag()

// MARK: - Initializer
Expand Down Expand Up @@ -146,8 +148,10 @@ private extension CreateViewitViewController {
func setupBinding() {
let input = ViewModel.Input(
urlStringChanged: urlTextRelay.eraseToAnyPublisher(),
next: nextRelay.eraseToAnyPublisher(),
descriptionChanged: contentTextRelay.eraseToAnyPublisher(),
upload: uploadButtonRelay.eraseToAnyPublisher()
upload: uploadButtonRelay.eraseToAnyPublisher(),
backgroundTap: backgroundTapRelay.eraseToAnyPublisher()
)

let output = viewModel.transform(input: input, cancelBag: cancelBag)
Expand All @@ -156,6 +160,13 @@ private extension CreateViewitViewController {
.assign(to: \.isEnabled, on: viewitInputView.nextButton)
.store(in: cancelBag)

output.isPossibleToURLUpload
.filter { $0 }
.sink { [weak self] _ in
self?.viewitInputView.descriptionTextField.becomeFirstResponder()
}
.store(in: cancelBag)

output.enableUpload
.assign(to: \.isEnabled, on: viewitInputView.uploadButton)
.store(in: cancelBag)
Expand All @@ -168,6 +179,12 @@ private extension CreateViewitViewController {
}
.store(in: cancelBag)

output.showSheetBeforeDismiss
.sink { [weak self] in
$0 ? self?.presentExitSheet() : self?.dismiss(animated: true)
}
.store(in: cancelBag)

output.errorMessage
.sink { [weak self] message in
let alertController = UIAlertController(title: "에러 발생!", message: message, preferredStyle: .alert)
Expand All @@ -181,13 +198,7 @@ private extension CreateViewitViewController {
// MARK: - Action Method

@objc func backgroundViewDidTap() {
let wableSheetViewController = WableSheetViewController(title: Constant.wableSheetTitle, message: nil)
let cancelAction = WableSheetAction(title: "취소", style: .gray)
let confirmAction = WableSheetAction(title: "나가기", style: .primary) { [weak self] in
self?.dismiss(animated: true)
}
wableSheetViewController.addActions(cancelAction, confirmAction)
present(wableSheetViewController, animated: true)
backgroundTapRelay.send()
}

@objc func urlTextFieldDidChange(_ sender: UITextField) {
Expand All @@ -197,7 +208,7 @@ private extension CreateViewitViewController {
}

@objc func nextButtonDidTap() {
viewitInputView.descriptionTextField.becomeFirstResponder()
nextRelay.send()
}

@objc func descriptionTextFieldDidChange(_ sender: UITextField) {
Expand Down Expand Up @@ -232,6 +243,16 @@ private extension CreateViewitViewController {
}
}

func presentExitSheet() {
let wableSheetViewController = WableSheetViewController(title: Constant.wableSheetTitle, message: nil)
let cancelAction = WableSheetAction(title: "취소", style: .gray)
let confirmAction = WableSheetAction(title: "나가기", style: .primary) { [weak self] in
self?.dismiss(animated: true)
}
wableSheetViewController.addActions(cancelAction, confirmAction)
present(wableSheetViewController, animated: true)
}

// MARK: - Constant

enum Constant {
Expand Down
Loading