diff --git a/Projects/App/ShareExtension/Sources/ShareRootFeature.swift b/Projects/App/ShareExtension/Sources/ShareRootFeature.swift index b63b2e44..0634c43d 100644 --- a/Projects/App/ShareExtension/Sources/ShareRootFeature.swift +++ b/Projects/App/ShareExtension/Sources/ShareRootFeature.swift @@ -106,10 +106,10 @@ struct ShareRootFeature { func handleAsyncAction(_ action: Action.AsyncAction, state: inout State) -> Effect { switch action { case .URL_파싱_수행: - guard let item = state.context?.inputItems.first as? NSExtensionItem, - let itemProvider = item.attachments?.first else { - return .none - } + guard + let item = state.context?.inputItems.first as? NSExtensionItem, + let itemProvider = item.attachments?.first + else { return .none } return .run { send in var urlItem: (any NSSecureCoding)? = nil diff --git a/Projects/App/Sources/AppDelegate/AppDelegateFeature.swift b/Projects/App/Sources/AppDelegate/AppDelegateFeature.swift index ace38842..2bb9afe9 100644 --- a/Projects/App/Sources/AppDelegate/AppDelegateFeature.swift +++ b/Projects/App/Sources/AppDelegate/AppDelegateFeature.swift @@ -60,7 +60,8 @@ public struct AppDelegateFeature { let setting = await self.userNotifications.getNotificationSettings() switch setting.authorizationStatus { case .authorized, .notDetermined: - guard try await self.userNotifications.requestAuthorization([.alert, .sound]) + guard + try await self.userNotifications.requestAuthorization([.alert, .sound]) else { return } default: return } diff --git a/Projects/App/Sources/MainTab/MainTabFeature.swift b/Projects/App/Sources/MainTab/MainTabFeature.swift index 3eaeed96..f0684502 100644 --- a/Projects/App/Sources/MainTab/MainTabFeature.swift +++ b/Projects/App/Sources/MainTab/MainTabFeature.swift @@ -41,7 +41,7 @@ public struct MainTabFeature { @Presents var contentDetail: ContentDetailFeature.State? @Shared(.inMemory("SelectCategory")) var categoryId: Int? @Shared(.inMemory("PushTapped")) var isPushTapped: Bool = false - var savedContentId: Int? + var categoryOfSavedContent: BaseCategoryItem? public init() { self.pokit = .init() @@ -78,6 +78,7 @@ public struct MainTabFeature { case 경고_띄움(BaseError) case errorSheetPresented(Bool) case 링크팝업_활성화(PokitLinkPopup.PopupType) + case 카테고리상세_이동(category: BaseCategoryItem) } public enum AsyncAction: Equatable { case 공유받은_카테고리_조회(categoryId: Int) @@ -98,7 +99,7 @@ public struct MainTabFeature { switch action { case .binding(\.linkPopup): guard state.linkPopup == nil else { return .none } - state.savedContentId = nil + state.categoryOfSavedContent = nil return .none case .binding: return .none @@ -183,15 +184,15 @@ private extension MainTabFeature { } ) case .onOpenURL(url: let url): - guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { - return .none - } + guard + let components = URLComponents(url: url, resolvingAgainstBaseURL: false) + else { return .none } let queryItems = components.queryItems ?? [] - guard let categoryIdString = queryItems.first(where: { $0.name == "categoryId" })?.value, - let categoryId = Int(categoryIdString) else { - return .none - } + guard + let categoryIdString = queryItems.first(where: { $0.name == "categoryId" })?.value, + let categoryId = Int(categoryIdString) + else { return .none } return .send(.async(.공유받은_카테고리_조회(categoryId: categoryId))) case .경고_확인버튼_클릭: @@ -222,7 +223,14 @@ private extension MainTabFeature { case let .링크팝업_활성화(type): state.linkPopup = type return .none - + case let .카테고리상세_이동(category): + if category.categoryName == "미분류" { + state.selectedTab = .pokit + state.path.removeAll() + return .send(.pokit(.delegate(.미분류_카테고리_활성화))) + } + state.path.append(.카테고리상세(.init(category: category))) + return .none default: return .none } } @@ -259,9 +267,9 @@ private extension MainTabFeature { return .send(.delegate(.링크추가하기)) case .success: state.linkPopup = nil - state.contentDetail = .init(contentId: state.savedContentId) - state.savedContentId = nil - return .none + guard let category = state.categoryOfSavedContent else { return .none } + state.categoryOfSavedContent = nil + return .send(.inner(.카테고리상세_이동(category: category))) case .error, .text, .warning, .none: return .none } diff --git a/Projects/App/Sources/MainTab/MainTabPath.swift b/Projects/App/Sources/MainTab/MainTabPath.swift index bfca29dc..21ec7cfd 100644 --- a/Projects/App/Sources/MainTab/MainTabPath.swift +++ b/Projects/App/Sources/MainTab/MainTabPath.swift @@ -141,8 +141,10 @@ public extension MainTabFeature { case .contentDetail(.presented(.delegate(.즐겨찾기_갱신_완료))), .contentDetail(.presented(.delegate(.컨텐츠_조회_완료))), .contentDetail(.presented(.delegate(.컨텐츠_삭제_완료))): - guard let stackElementId = state.path.ids.last, - let lastPath = state.path.last else { + guard + let stackElementId = state.path.ids.last, + let lastPath = state.path.last + else { switch state.selectedTab { case .pokit: return .send(.pokit(.delegate(.미분류_카테고리_컨텐츠_조회))) @@ -175,7 +177,7 @@ public extension MainTabFeature { /// - 링크추가 및 수정에서 저장하기 눌렀을 때 case let .path(.element(stackElementId, action: .링크추가및수정(.delegate(.저장하기_완료(contentId))))): - state.savedContentId = contentId + state.categoryOfSavedContent = contentId state.path.removeLast() switch state.path.last { case .검색: diff --git a/Projects/CoreKit/Sources/CoreNetwork/TokenInterceptor.swift b/Projects/CoreKit/Sources/CoreNetwork/TokenInterceptor.swift index 037792af..ea5eaaf4 100644 --- a/Projects/CoreKit/Sources/CoreNetwork/TokenInterceptor.swift +++ b/Projects/CoreKit/Sources/CoreNetwork/TokenInterceptor.swift @@ -44,16 +44,20 @@ public final class TokenInterceptor: RequestInterceptor { dueTo error: Error, completion: @escaping (RetryResult) -> Void ) { - guard let response = request.task?.response as? HTTPURLResponse, - response.statusCode == 401 else { + guard + let response = request.task?.response as? HTTPURLResponse, + response.statusCode == 401 + else { completion(.doNotRetryWithError(error)) return } print("🚀 Retry: statusCode: \(response.statusCode)") - guard keychain.read(.accessToken) != nil, - let refreshToken = keychain.read(.refreshToken) else { + guard + keychain.read(.accessToken) != nil, + let refreshToken = keychain.read(.refreshToken) + else { deleteAllToken() completion(.doNotRetryWithError(error)) return diff --git a/Projects/CoreKit/Sources/Data/Client/KakaoSDK/Share/KakaoShareClient+LiveKey.swift b/Projects/CoreKit/Sources/Data/Client/KakaoSDK/Share/KakaoShareClient+LiveKey.swift index a27d8a0a..925454c3 100644 --- a/Projects/CoreKit/Sources/Data/Client/KakaoSDK/Share/KakaoShareClient+LiveKey.swift +++ b/Projects/CoreKit/Sources/Data/Client/KakaoSDK/Share/KakaoShareClient+LiveKey.swift @@ -45,14 +45,16 @@ extension KakaoShareClient: DependencyKey { buttons: [button] ) - guard ShareApi.isKakaoTalkSharingAvailable(), - let templateJsonData = try? SdkJSONEncoder.custom.encode(template), - let templateJsonObject = SdkUtils.toJsonObject(templateJsonData) else { + guard + ShareApi.isKakaoTalkSharingAvailable(), + let templateJsonData = try? SdkJSONEncoder.custom.encode(template), + let templateJsonObject = SdkUtils.toJsonObject(templateJsonData) + else { /// 🚨 Error Case [1]: 카카오톡 미설치 - guard let url = URL(string: "itms-apps://itunes.apple.com/app/id362057947"), - UIApplication.shared.canOpenURL(url) else { - return - } + guard + let url = URL(string: "itms-apps://itunes.apple.com/app/id362057947"), + UIApplication.shared.canOpenURL(url) + else { return } UIApplication.shared.open(url, options: [:], completionHandler: nil) return diff --git a/Projects/CoreKit/Sources/Data/Client/SocialLogin/Controller/AppleLoginController.swift b/Projects/CoreKit/Sources/Data/Client/SocialLogin/Controller/AppleLoginController.swift index f8c243c9..fda12a78 100644 --- a/Projects/CoreKit/Sources/Data/Client/SocialLogin/Controller/AppleLoginController.swift +++ b/Projects/CoreKit/Sources/Data/Client/SocialLogin/Controller/AppleLoginController.swift @@ -33,22 +33,28 @@ public final class AppleLoginController: NSObject, ASAuthorizationControllerDele controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization ) { - guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else { + guard + let credential = authorization.credential as? ASAuthorizationAppleIDCredential + else { continuation?.resume(throwing: SocialLoginError.invalidCredential) continuation = nil return } - guard let tokenData = credential.identityToken, - let token = String(data: tokenData, encoding: .utf8) else { + guard + let tokenData = credential.identityToken, + let token = String(data: tokenData, encoding: .utf8) + else { continuation?.resume(throwing: SocialLoginError.appleLoginError(.invalidIdentityToken)) continuation = nil return } - guard let authorizationCode = credential.authorizationCode, - let codeString = String(data: authorizationCode, encoding: .utf8) else { + guard + let authorizationCode = credential.authorizationCode, + let codeString = String(data: authorizationCode, encoding: .utf8) + else { continuation?.resume(throwing: SocialLoginError.appleLoginError(.invalidAuthorizationCode)) continuation = nil return diff --git a/Projects/DSKit/Sources/Components/PokitBadge.swift b/Projects/DSKit/Sources/Components/PokitBadge.swift index a535b046..adace557 100644 --- a/Projects/DSKit/Sources/Components/PokitBadge.swift +++ b/Projects/DSKit/Sources/Components/PokitBadge.swift @@ -19,12 +19,6 @@ public struct PokitBadge: View { .background { RoundedRectangle(cornerRadius: 4, style: .continuous) .fill(backgroundColor) - .overlay { - if state == .unRead { - RoundedRectangle(cornerRadius: 4, style: .continuous) - .stroke(.pokit(.border(.brand)), lineWidth: 1) - } - } } } @@ -32,7 +26,7 @@ public struct PokitBadge: View { switch self.state { case .default, .small, .memo, .member: return .pokit(.bg(.primary)) case .unCategorized: return .pokit(.color(.grayScale(._50))) - case .unRead: return .pokit(.bg(.base)) + case .unRead: return Color(red: 1, green: 0.95, blue: 0.92) } } diff --git a/Projects/DSKit/Sources/Components/PokitCalendar.swift b/Projects/DSKit/Sources/Components/PokitCalendar.swift index c4f58435..f3e95270 100644 --- a/Projects/DSKit/Sources/Components/PokitCalendar.swift +++ b/Projects/DSKit/Sources/Components/PokitCalendar.swift @@ -208,13 +208,13 @@ public struct PokitCalendar: View { let year = calendar.component(.year, from: date) let month = calendar.component(.month, from: date) - guard let range = calendar.range( - of: .day, - in: .month, - for: date - ) else { - return dates - } + guard + let range = calendar.range( + of: .day, + in: .month, + for: date + ) + else { return dates } dates = range.map { day in var components = DateComponents() @@ -255,13 +255,13 @@ public struct PokitCalendar: View { return dates } - guard let monthRange = calendar.range( - of: .day, - in: .month, - for: monthDate - ) else { - return dates - } + guard + let monthRange = calendar.range( + of: .day, + in: .month, + for: monthDate + ) + else { return dates } let monthDays = Array(monthRange).suffix(firstWeekday - 1) @@ -369,25 +369,24 @@ public struct PokitCalendar: View { } private func beforeButtonTapped() { - guard let date = calendar.date( - byAdding: .month, - value: -1, - to: currentDate - ) else { - return - } + guard + let date = calendar.date( + byAdding: .month, + value: -1, + to: currentDate + ) else { return } self.page = formatter.string(from: date) } private func nextButtonTapped() { - guard let date = calendar.date( - byAdding: .month, - value: 1, - to: currentDate - ) else { - return - } + guard + let date = calendar.date( + byAdding: .month, + value: 1, + to: currentDate + ) + else { return } self.page = formatter.string(from: date) } diff --git a/Projects/DSKit/Sources/Components/PokitLinkCard.swift b/Projects/DSKit/Sources/Components/PokitLinkCard.swift index a77c7e54..7ff26664 100644 --- a/Projects/DSKit/Sources/Components/PokitLinkCard.swift +++ b/Projects/DSKit/Sources/Components/PokitLinkCard.swift @@ -135,16 +135,16 @@ public struct PokitLinkCard: View { let isUnCategorized = link.categoryName == "미분류" HStack(spacing: 6) { - if let isRead = link.isRead, !isRead { - PokitBadge(state: .unRead) - } - PokitBadge( state: isUnCategorized ? .unCategorized : .default(link.categoryName) ) + if let isRead = link.isRead, !isRead { + PokitBadge(state: .unRead) + } + if let memo = link.memo, !memo.isEmpty { PokitBadge(state: .memo) } diff --git a/Projects/DSKit/Sources/Components/PokitLinkPreview.swift b/Projects/DSKit/Sources/Components/PokitLinkPreview.swift index 844619af..46a22a0c 100644 --- a/Projects/DSKit/Sources/Components/PokitLinkPreview.swift +++ b/Projects/DSKit/Sources/Components/PokitLinkPreview.swift @@ -7,6 +7,7 @@ import SwiftUI +import Util import NukeUI public struct PokitLinkPreview: View { @@ -33,7 +34,6 @@ public struct PokitLinkPreview: View { } } - private var buttonLabel: some View { HStack(spacing: 16) { Group { @@ -54,18 +54,13 @@ public struct PokitLinkPreview: View { .background { RoundedRectangle(cornerRadius: 12, style: .continuous) .fill(.pokit(.bg(.base))) + .shadow(color: .black.opacity(0.06), radius: 3, x: 2, y: 2) } .overlay { RoundedRectangle(cornerRadius: 12, style: .continuous) .stroke(.pokit(.border(.tertiary)), lineWidth: 1) } - .shadow(color: .black.opacity(0.06), radius: 3, x: 2, y: 2) - .onAppear { - withAnimation { - UINotificationFeedbackGenerator() - .notificationOccurred(.success) - } - } + .onChange(of: imageURL, perform: onChangeImageURL) } @MainActor @@ -134,6 +129,13 @@ public struct PokitLinkPreview: View { else { return } openURL(url) } + + private func onChangeImageURL(_ imageURL: String?) { + guard imageURL != nil else { return } + let isError = title == Constants.제목을_입력해주세요_문구 + UINotificationFeedbackGenerator() + .notificationOccurred(isError ? .error : .success) + } } #Preview { diff --git a/Projects/DSKit/Sources/Components/PokitList.swift b/Projects/DSKit/Sources/Components/PokitList.swift index 04e39fcb..ac0b3a76 100644 --- a/Projects/DSKit/Sources/Components/PokitList.swift +++ b/Projects/DSKit/Sources/Components/PokitList.swift @@ -81,7 +81,7 @@ public struct PokitList: View { Spacer() } - .padding(.vertical, 18) + .padding(.vertical, 12) .padding(.horizontal, 20) .background { if isSelected { diff --git a/Projects/DSKit/Sources/Components/PokitListButton.swift b/Projects/DSKit/Sources/Components/PokitListButton.swift index bf1fb69e..67ef0dfd 100644 --- a/Projects/DSKit/Sources/Components/PokitListButton.swift +++ b/Projects/DSKit/Sources/Components/PokitListButton.swift @@ -38,7 +38,7 @@ public struct PokitListButton: View { HStack { switch type { case let .default(icon, iconColor), - let .bottomSheet(icon, iconColor), + let .bottomSheet(icon, iconColor, _), let .subText(icon, iconColor, _): Text(title) .pokitFont(.b1(.m)) @@ -76,9 +76,9 @@ public struct PokitListButton: View { } } .padding(.horizontal, 24) - .padding(.vertical, 16) + .padding(.vertical, 20) .background(alignment: .bottom) { - if case .bottomSheet = type { + if case let .bottomSheet(_, _, isLast) = type, !isLast { Rectangle() .fill(.pokit(.border(.tertiary))) .frame(height: 1) @@ -90,10 +90,9 @@ public struct PokitListButton: View { extension PokitListButton { public enum ListButtonType { case `default`(icon: PokitImage, iconColor: Color) - case bottomSheet(icon: PokitImage, iconColor: Color) + case bottomSheet(icon: PokitImage, iconColor: Color, isLast: Bool = false) case subText(icon: PokitImage, iconColor: Color, subeText: String) case toggle(subeText: String) - } } diff --git a/Projects/DSKit/Sources/Components/PokitSelect.swift b/Projects/DSKit/Sources/Components/PokitSelect.swift index 853b1547..2754573c 100644 --- a/Projects/DSKit/Sources/Components/PokitSelect.swift +++ b/Projects/DSKit/Sources/Components/PokitSelect.swift @@ -53,7 +53,7 @@ public struct PokitSelect: View { listSheet .presentationDragIndicator(.visible) .pokitPresentationCornerRadius() - .presentationDetents([.medium]) + .presentationDetents([.height(564)]) .pokitPresentationBackground() } } @@ -115,7 +115,7 @@ public struct PokitSelect: View { listCellTapped(item) } } - .padding(.top, 24) + .padding(.top, 12) .padding(.bottom, 20) } else { PokitLoading() diff --git a/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift b/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift index f5d2b9fd..c9ce9e5d 100644 --- a/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift +++ b/Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailFeature.swift @@ -225,9 +225,11 @@ private extension CategoryDetailFeature { case let .카테고리_목록_조회_API_반영(response): state.domain.categoryListInQuiry = response - guard let first = response.data?.first(where: { item in - item.id == state.domain.category.id - }) else { return .none } + guard + let first = response.data?.first(where: { item in + item.id == state.domain.category.id + }) + else { return .none } state.domain.category = first return .none diff --git a/Projects/Feature/FeatureContentDetail/Sources/ContentDetail/ContentDetailFeature.swift b/Projects/Feature/FeatureContentDetail/Sources/ContentDetail/ContentDetailFeature.swift index a9047819..af6ef08e 100644 --- a/Projects/Feature/FeatureContentDetail/Sources/ContentDetail/ContentDetailFeature.swift +++ b/Projects/Feature/FeatureContentDetail/Sources/ContentDetail/ContentDetailFeature.swift @@ -69,10 +69,12 @@ public struct ContentDetailFeature { case 삭제_버튼_눌렀을때 case 삭제확인_버튼_눌렀을때 case 즐겨찾기_버튼_눌렀을때 + case 키보드_취소_버튼_눌렀을때 + case 키보드_완료_버튼_눌렀울때 + case 경고시트_해제 case 링크_공유_완료되었을때 - case 메모포커스_변경되었을때(Bool) } public enum InnerAction: Equatable { @@ -166,10 +168,10 @@ private extension ContentDetailFeature { case .binding: return .none case .즐겨찾기_버튼_눌렀을때: - guard let content = state.domain.content, - let favorites = state.domain.content?.favorites else { - return .none - } + guard + let content = state.domain.content, + let favorites = state.domain.content?.favorites + else { return .none } return favorites ? .send(.async(.즐겨찾기_취소_API(id: content.id))) : .send(.async(.즐겨찾기_API(id: content.id))) @@ -179,12 +181,12 @@ private extension ContentDetailFeature { case .경고시트_해제: state.showAlert = false return .none - case let .메모포커스_변경되었을때(isFocused): - guard - !isFocused, - state.memo != state.domain.content?.memo - else { return .none } + case .키보드_취소_버튼_눌렀을때: + state.memo = state.domain.content?.memo ?? "" + return .none + case .키보드_완료_버튼_눌렀울때: let memo = state.memo + guard memo != state.domain.content?.memo else { return .none } state.domain.content?.memo = memo return .send(.async(.컨텐츠_수정_API)) } diff --git a/Projects/Feature/FeatureContentDetail/Sources/ContentDetail/ContentDetailView.swift b/Projects/Feature/FeatureContentDetail/Sources/ContentDetail/ContentDetailView.swift index ea08e3ff..b0247281 100644 --- a/Projects/Feature/FeatureContentDetail/Sources/ContentDetail/ContentDetailView.swift +++ b/Projects/Feature/FeatureContentDetail/Sources/ContentDetail/ContentDetailView.swift @@ -34,8 +34,9 @@ public extension ContentDetailView { VStack(spacing: 0) { contentMemo - Divider() + Rectangle() .foregroundStyle(.pokit(.border(.tertiary))) + .frame(height: 1) bottomList(favorites: favorites) } @@ -56,8 +57,6 @@ public extension ContentDetailView { PokitLinkPopup(type: $store.linkPopup) } } - .dismissKeyboard(focused: $isFocused) - .onChange(of: isFocused) { send(.메모포커스_변경되었을때($0)) } .sheet(isPresented: $store.showAlert) { PokitAlert( "링크를 정말 삭제하시겠습니까?", @@ -110,8 +109,6 @@ private extension ContentDetailView { func title(content: BaseContentDetail) -> some View { VStack(alignment: .leading, spacing: 8) { Group { - remindAndBadge(content: content) - Text(content.title) .pokitFont(.title3) .foregroundStyle(.pokit(.text(.primary))) @@ -119,6 +116,8 @@ private extension ContentDetailView { .lineLimit(2) HStack { + remindAndBadge(content: content) + Spacer() Text(content.createdAt) @@ -128,8 +127,9 @@ private extension ContentDetailView { } .padding(.horizontal, 20) - Divider() + Rectangle() .foregroundStyle(.pokit(.border(.tertiary))) + .frame(height: 1) .padding(.top, 4) } } @@ -159,12 +159,29 @@ private extension ContentDetailView { focusState: $isFocused, equals: true ) + .toolbar { keyboardToolBar } .frame(minHeight: isFocused ? 164 : 132) .animation(.pokitDissolve, value: isFocused) } .padding(.bottom, 24) .padding(.horizontal, 20) } + + var keyboardToolBar: some ToolbarContent { + ToolbarItemGroup(placement: .keyboard) { + Button("취소") { + isFocused = false + send(.키보드_취소_버튼_눌렀을때) + } + + Spacer() + + Button("완료") { + isFocused = false + send(.키보드_완료_버튼_눌렀울때) + } + } + } @ViewBuilder func bottomList(favorites: Bool) -> some View { @@ -204,7 +221,8 @@ private extension ContentDetailView { title: "삭제하기", type: .bottomSheet( icon: .icon(.trash), - iconColor: .pokit(.icon(.primary)) + iconColor: .pokit(.icon(.primary)), + isLast: true ), action: { send(.삭제_버튼_눌렀을때) } ) diff --git a/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingFeature.swift b/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingFeature.swift index a4783ab6..725c53ac 100644 --- a/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingFeature.swift +++ b/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingFeature.swift @@ -50,10 +50,6 @@ public struct ContentSettingFeature { get { domain.memo } set { domain.memo = newValue } } - var isRemind: BaseContentDetail.RemindState { - get { domain.alertYn } - set { domain.alertYn = newValue } - } var content: BaseContentDetail? { get { domain.content } } @@ -127,7 +123,7 @@ public struct ContentSettingFeature { public enum ScopeAction: Equatable { case 없음 } public enum DelegateAction: Equatable { - case 저장하기_완료(contentId: Int) + case 저장하기_완료(category: BaseCategoryItem) case 포킷추가하기 case dismiss } @@ -224,9 +220,10 @@ private extension ContentSettingFeature { return .send(.inner(.링크복사_반영(state.link))) case .링크지우기_버튼_눌렀을때: state.domain.data = "" + state.domain.title = "" return .none case .제목지우기_버튼_눌렀을때: - state.title = "" + state.domain.title = "" return .none } } @@ -258,16 +255,18 @@ private extension ContentSettingFeature { let contentTitle = state.title.isEmpty ? Constants.제목을_입력해주세요_문구 : state.title - state.linkTitle = title ?? contentTitle state.linkImageURL = imageURL ?? Constants.기본_썸네일_주소.absoluteString + state.linkTitle = title ?? contentTitle if let title, state.domain.title.isEmpty { state.domain.title = title } state.domain.thumbNail = imageURL return .send(.inner(.linkPreview), animation: .pokitDissolve) case .URL_유효성_확인: - guard let url = URL(string: state.domain.data), - !state.domain.data.isEmpty else { + guard + let url = URL(string: state.domain.data), + !state.domain.data.isEmpty + else { /// 🚨 Error Case [1]: 올바른 링크가 아닐 때 state.linkPopup = nil state.linkTitle = nil @@ -312,8 +311,16 @@ private extension ContentSettingFeature { /// - `카테고리_목록_조회`의 filter 옵션을 `false`로 해두었기 때문에 `미분류` 카테고리 또한 항목에서 조회가 가능함 /// [1]. `미분류`에 해당하는 인덱스 번호와 항목을 체크, 없다면 목록갱신이 불가함 - guard let unclassifiedItemIdx = categoryList.data?.firstIndex(where: { $0.categoryName == "미분류" }) else { return .none } - guard let unclassifiedItem = categoryList.data?.first(where: { $0.categoryName == "미분류" }) else { return .none } + guard + let unclassifiedItemIdx = categoryList.data?.firstIndex(where: { + $0.categoryName == "미분류" + }) + else { return .none } + guard + let unclassifiedItem = categoryList.data?.first(where: { + $0.categoryName == "미분류" + }) + else { return .none } /// [2]. 새로운 list변수를 만들어주고 카테고리 항목 순서를 재배치 (최신순 정렬 시 미분류는 항상 맨 마지막) var list = categoryList @@ -376,10 +383,13 @@ private extension ContentSettingFeature { categoryListFetch(request: request) ) case .컨텐츠_수정_API: - guard let contentId = state.domain.contentId, - let categoryId = state.selectedPokit?.id else { - return .none - } + guard + let contentId = state.domain.contentId, + let categoryId = state.selectedPokit?.id, + let category = state.domain.categoryListInQuiry.data?.first(where: { + $0.id == categoryId + }) + else { return .none } let request = ContentBaseRequest( data: state.domain.data, title: state.domain.title, @@ -394,14 +404,17 @@ private extension ContentSettingFeature { request ) await send(.inner(.선택한_포킷_인메모리_삭제)) - await send(.delegate(.저장하기_완료(contentId: contentId))) + await send(.delegate(.저장하기_완료(category: category))) } catch: { error, send in await send(.inner(.error(error))) } case .컨텐츠_추가_API: - guard let categoryId = state.selectedPokit?.id else { - return .none - } + guard + let categoryId = state.selectedPokit?.id, + let category = state.domain.categoryListInQuiry.data?.first(where: { + $0.id == categoryId + }) + else { return .none } let request = ContentBaseRequest( data: state.domain.data, title: state.domain.title, @@ -413,7 +426,7 @@ private extension ContentSettingFeature { return .run { send in let content = try await contentClient.컨텐츠_추가(request) await send(.inner(.선택한_포킷_인메모리_삭제)) - await send(.delegate(.저장하기_완료(contentId: content.contentId))) + await send(.delegate(.저장하기_완료(category: category))) } catch: { error, send in await send(.inner(.error(error))) } diff --git a/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingView.swift b/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingView.swift index 158c8bb4..eb67c068 100644 --- a/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingView.swift +++ b/Projects/Feature/FeatureContentSetting/Sources/ContentSetting/ContentSettingView.swift @@ -40,8 +40,6 @@ public extension ContentSettingView { pokitSelectButton memoTextArea - - remindSwitchRadio } .padding(.horizontal, 20) .padding(.top, 16) @@ -116,6 +114,7 @@ private extension ContentSettingView { ), shape: .rectangle, state: $store.linkTextInputState, + placeholder: "링크를 입력해주세요.", focusState: $focusedType, equals: .link ) @@ -133,6 +132,7 @@ private extension ContentSettingView { ), shape: .rectangle, state: $store.titleTextInpuState, + placeholder: "제목을 입력해주세요.", focusState: $focusedType, equals: .title ) @@ -153,44 +153,12 @@ private extension ContentSettingView { text: $store.memo, label: "메모", state: $store.memoTextAreaState, + placeholder: "메모를 입력해주세요.", focusState: $focusedType, equals: .memo ) .frame(height: 192) } - - var remindSwitchRadio: some View { - VStack(alignment: .leading, spacing: 0) { - Text("리마인드 알림을 보내드릴까요?") - .pokitFont(.b2(.m)) - .foregroundStyle(.pokit(.text(.secondary))) - .padding(.bottom, 12) - - PokitSwitchRadio { - PokitPartSwitchRadio( - labelText: "안받을래요", - selection: $store.isRemind, - to: .no, - style: .stroke - ) - .background() - - PokitPartSwitchRadio( - labelText: "받을래요", - selection: $store.isRemind, - to: .yes, - style: .stroke - ) - .background() - } - .padding(.bottom, 8) - - Text("일주일 후에 알림을 전송해드립니다") - .pokitFont(.detail1) - .foregroundStyle(.pokit(.text(.tertiary))) - } - .padding(.bottom, 16) - } } private extension ContentSettingView { enum FocusedType: Equatable { diff --git a/Projects/Feature/FeaturePokit/Sources/PokitRootFeature.swift b/Projects/Feature/FeaturePokit/Sources/PokitRootFeature.swift index 059db362..f37ec1dd 100644 --- a/Projects/Feature/FeaturePokit/Sources/PokitRootFeature.swift +++ b/Projects/Feature/FeaturePokit/Sources/PokitRootFeature.swift @@ -123,6 +123,8 @@ public struct PokitRootFeature { case 포킷추가_버튼_눌렀을때 case 링크추가_버튼_눌렀을때 + + case 미분류_카테고리_활성화 } } @@ -313,9 +315,11 @@ private extension PokitRootFeature { return .none case let .미분류_카테고리_컨텐츠_삭제_API_반영(contentId: contentId): - guard let index = state.domain.unclassifiedContentList.data?.firstIndex(where: { $0.id == contentId }) else { - return .none - } + guard + let index = state.domain.unclassifiedContentList.data?.firstIndex(where: { + $0.id == contentId + }) + else { return .none } state.domain.unclassifiedContentList.data?.remove(at: index) state.contents.removeAll { $0.content.id == contentId } state.isPokitDeleteSheetPresented = false @@ -513,6 +517,10 @@ private extension PokitRootFeature { default: return .none } + case .미분류_카테고리_활성화: + state.folderType = .folder(.미분류) + state.sortType = .sort(.최신순) + return .send(.inner(.sort)) default: return .none } diff --git a/Projects/Feature/FeaturePokit/Sources/PokitRootView.swift b/Projects/Feature/FeaturePokit/Sources/PokitRootView.swift index 6766234f..66c4b556 100644 --- a/Projects/Feature/FeaturePokit/Sources/PokitRootView.swift +++ b/Projects/Feature/FeaturePokit/Sources/PokitRootView.swift @@ -86,13 +86,15 @@ private extension PokitRootView { Spacer() - PokitIconLTextLink( - store.sortType == .sort(.최신순) ? - "최신순" : store.folderType == .folder(.포킷) ? "이름순" : "오래된순", - icon: .icon(.align), - action: { send(.분류_버튼_눌렀을때) } - ) - .contentTransition(.numericText()) + if !store.contents.isEmpty { + PokitIconLTextLink( + store.sortType == .sort(.최신순) ? + "최신순" : store.folderType == .folder(.포킷) ? "이름순" : "오래된순", + icon: .icon(.align), + action: { send(.분류_버튼_눌렀을때) } + ) + .contentTransition(.numericText()) + } } .animation(.snappy(duration: 0.7), value: store.folderType) } diff --git a/Projects/Feature/FeatureRemind/Sources/Remind/RemindFeature.swift b/Projects/Feature/FeatureRemind/Sources/Remind/RemindFeature.swift index 50b8f1ba..4638db6b 100644 --- a/Projects/Feature/FeatureRemind/Sources/Remind/RemindFeature.swift +++ b/Projects/Feature/FeatureRemind/Sources/Remind/RemindFeature.swift @@ -231,11 +231,11 @@ private extension RemindFeature { } case let .즐겨찾기_이미지_조회_수행(contentId): return .run { [favoriteContents = state.favoriteContents] send in - guard let index = favoriteContents?.index(id: contentId), - let content = favoriteContents?[index], - let url = URL(string: content.data) else { - return - } + guard + let index = favoriteContents?.index(id: contentId), + let content = favoriteContents?[index], + let url = URL(string: content.data) + else { return } let imageURL = try await swiftSoupClient.parseOGImageURL(url) guard let imageURL else { return } @@ -247,11 +247,11 @@ private extension RemindFeature { } case let .읽지않음_이미지_조회_수행(contentId): return .run { [unreadContents = state.unreadContents] send in - guard let index = unreadContents?.index(id: contentId), - let content = unreadContents?[index], - let url = URL(string: content.data) else { - return - } + guard + let index = unreadContents?.index(id: contentId), + let content = unreadContents?[index], + let url = URL(string: content.data) + else { return } let imageURL = try await swiftSoupClient.parseOGImageURL(url) guard let imageURL else { return } @@ -262,11 +262,11 @@ private extension RemindFeature { } case let .리마인드_이미지_조회_수행(contentId): return .run { [recommendedContents = state.recommendedContents] send in - guard let index = recommendedContents?.index(id: contentId), - let content = recommendedContents?[index], - let url = URL(string: content.data) else { - return - } + guard + let index = recommendedContents?.index(id: contentId), + let content = recommendedContents?[index], + let url = URL(string: content.data) + else { return } let imageURL = try await swiftSoupClient.parseOGImageURL(url) guard let imageURL else { return } diff --git a/Projects/Feature/FeatureSetting/Sources/Alert/PokitAlertBoxFeature.swift b/Projects/Feature/FeatureSetting/Sources/Alert/PokitAlertBoxFeature.swift index ff647e64..5744b502 100644 --- a/Projects/Feature/FeatureSetting/Sources/Alert/PokitAlertBoxFeature.swift +++ b/Projects/Feature/FeatureSetting/Sources/Alert/PokitAlertBoxFeature.swift @@ -146,7 +146,11 @@ private extension PokitAlertBoxFeature { return .none case let .알람_삭제_API_반영(item): - guard let idx = state.domain.alertList.data?.firstIndex(where: { $0 == item }) else { return .none } + guard + let idx = state.domain.alertList.data?.firstIndex(where: { + $0 == item + }) + else { return .none } state.domain.alertList.data?.remove(at: idx) return .none } diff --git a/Projects/Feature/FeatureSetting/Sources/Search/PokitSearchFeature.swift b/Projects/Feature/FeatureSetting/Sources/Search/PokitSearchFeature.swift index 706cb9f7..1c991664 100644 --- a/Projects/Feature/FeatureSetting/Sources/Search/PokitSearchFeature.swift +++ b/Projects/Feature/FeatureSetting/Sources/Search/PokitSearchFeature.swift @@ -236,10 +236,11 @@ private extension PokitSearchFeature { return .send(.inner(.filterBottomSheet(filterType: .contentType))) case .기간_버튼_눌렀을때: - guard state.domain.condition.startDate != nil && state.domain.condition.endDate != nil else { + guard + state.domain.condition.startDate != nil && + state.domain.condition.endDate != nil /// - 선택된 기간이 없을 경우 - return .send(.inner(.filterBottomSheet(filterType: .date))) - } + else { return .send(.inner(.filterBottomSheet(filterType: .date))) } state.domain.condition.startDate = nil state.domain.condition.endDate = nil return .run { send in @@ -323,8 +324,7 @@ private extension PokitSearchFeature { state.domain.condition.startDate = startDate state.domain.condition.endDate = endDate - guard let startDate, - let endDate else { + guard let startDate, let endDate else { /// 🚨 Error Case : 날짜 필터가 선택 안되었을 경우 state.dateFilterText = "기간" return .none @@ -513,9 +513,10 @@ private extension PokitSearchFeature { func handleDelegateAction(_ action: Action.DelegateAction, state: inout State) -> Effect { switch action { case .컨텐츠_검색: - guard let contentList = state.domain.contentList.data, !contentList.isEmpty else { - return .none - } + guard + let contentList = state.domain.contentList.data, + !contentList.isEmpty + else { return .none } return .send(.async(.컨텐츠_검색_API), animation: .pokitSpring) default: return .none }