diff --git a/DontForget.xcodeproj/project.pbxproj b/DontForget.xcodeproj/project.pbxproj index da8542d..57cf928 100644 --- a/DontForget.xcodeproj/project.pbxproj +++ b/DontForget.xcodeproj/project.pbxproj @@ -48,6 +48,7 @@ AD147F322B57FFB500561846 /* AppIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD147F312B57FFB500561846 /* AppIntent.swift */; }; AD147F342B57FFB600561846 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AD147F332B57FFB600561846 /* Assets.xcassets */; }; AD147F382B57FFB600561846 /* DontForgetWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = AD147F262B57FFB500561846 /* DontForgetWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + AD1D31F92B9589A700ECCBFD /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD1D31F82B9589A700ECCBFD /* NetworkMonitor.swift */; }; AD36D44B2B8DD37D00250705 /* DeletionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD36D44A2B8DD37D00250705 /* DeletionRepository.swift */; }; AD36D44D2B8DD39500250705 /* DeletionInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD36D44C2B8DD39500250705 /* DeletionInterface.swift */; }; AD3B45852B68B634009529DE /* AnniversaryDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD3B45842B68B634009529DE /* AnniversaryDetailView.swift */; }; @@ -152,6 +153,7 @@ AD147F312B57FFB500561846 /* AppIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntent.swift; sourceTree = ""; }; AD147F332B57FFB600561846 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; AD147F352B57FFB600561846 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AD1D31F82B9589A700ECCBFD /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = ""; }; AD36D44A2B8DD37D00250705 /* DeletionRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletionRepository.swift; sourceTree = ""; }; AD36D44C2B8DD39500250705 /* DeletionInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletionInterface.swift; sourceTree = ""; }; AD3B45842B68B634009529DE /* AnniversaryDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnniversaryDetailView.swift; sourceTree = ""; }; @@ -251,6 +253,7 @@ children = ( 09642C3A2B649B260015220E /* EndPoint.swift */, 09642C3C2B649B2D0015220E /* APIConstant.swift */, + AD1D31F82B9589A700ECCBFD /* NetworkMonitor.swift */, ); path = Network; sourceTree = ""; @@ -756,6 +759,7 @@ 09642C462B649FD00015220E /* DTO.swift in Sources */, AD0F99332B7C523200E2A915 /* AlarmView.swift in Sources */, 09642C582B67C8730015220E /* ViewModelType.swift in Sources */, + AD1D31F92B9589A700ECCBFD /* NetworkMonitor.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -940,7 +944,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.2; + MARKETING_VERSION = 1.1.0; "OTHER_LDFLAGS[arch=*]" = "-Wl,-ld_classic"; PRODUCT_BUNDLE_IDENTIFIER = com.dazzling.DontForget; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -981,7 +985,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.2; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.dazzling.DontForget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1014,7 +1018,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.2; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.dazzling.DontForget.DontForgetWidget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1049,7 +1053,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.2; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.dazzling.DontForget.DontForgetWidget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/DontForget/Sources/Data/Network/NetworkMonitor.swift b/DontForget/Sources/Data/Network/NetworkMonitor.swift new file mode 100644 index 0000000..ad3089d --- /dev/null +++ b/DontForget/Sources/Data/Network/NetworkMonitor.swift @@ -0,0 +1,24 @@ +// +// NetworkMonitor.swift +// DontForget +// +// Created by 제나 on 3/4/24. +// + +import Foundation +import Network + +@Observable +final class NetworkMonitor: ObservableObject { + static let shared = NetworkMonitor() + private let networkMonitor = NWPathMonitor() + private let workerQueue = DispatchQueue(label: "Monitor") + var isConnected = false + + init() { + networkMonitor.pathUpdateHandler = { path in + self.isConnected = path.status == .satisfied + } + networkMonitor.start(queue: workerQueue) + } +} diff --git a/DontForget/Sources/Domain/Model/CardType.swift b/DontForget/Sources/Domain/Model/CardType.swift index cbac3d2..a7f132c 100644 --- a/DontForget/Sources/Domain/Model/CardType.swift +++ b/DontForget/Sources/Domain/Model/CardType.swift @@ -7,7 +7,7 @@ import Foundation -enum CardType: String { +enum CardType: String, CaseIterable { case lunar = "LUNAR" case face = "FACE" case arm = "ARM" diff --git a/DontForget/Sources/Presentations/Application/Common/Constants.swift b/DontForget/Sources/Presentations/Application/Common/Constants.swift index 8e23b4f..698957a 100644 --- a/DontForget/Sources/Presentations/Application/Common/Constants.swift +++ b/DontForget/Sources/Presentations/Application/Common/Constants.swift @@ -20,6 +20,6 @@ struct Constants { let calendar = Calendar.current let startOfCurrentDate = calendar.startOfDay(for: Date()) let startOfTargetDate = calendar.startOfDay(for: from) - return calendar.dateComponents([.day], from: startOfCurrentDate, to: startOfTargetDate).day! + return calendar.dateComponents([.day], from: startOfCurrentDate, to: startOfTargetDate).day! * -1 } } diff --git a/DontForget/Sources/Presentations/Application/Creation/CreationView.swift b/DontForget/Sources/Presentations/Application/Creation/CreationView.swift index a24160f..f135beb 100644 --- a/DontForget/Sources/Presentations/Application/Creation/CreationView.swift +++ b/DontForget/Sources/Presentations/Application/Creation/CreationView.swift @@ -46,10 +46,7 @@ struct CreationView: View { ) { self.viewModel = viewModel self.type = type - switch type { - case .create: - break - case .edit: + if let id = id { self.id = id } configure() @@ -194,60 +191,8 @@ struct CreationView: View { .padding(.top, isKeyboardVisible && focusField == .memo ? keyboardHeight + 20 : 0) .animation(.default, value: keyboardHeight) } - .onAppear { - switch type { - case .create: - self.selectedAlarmIndexes = Set([AlarmPeriod.dDay.schedule]) - focusField = .eventName - case .edit: - viewModel.fetchAnniversaryDetail(id: id!) - viewModel.$anniversaryDetail - .receive(on: DispatchQueue.main) - .sink { res in - self.name = res?.title ?? "" - self.memo = res?.content ?? "" - self.selectedAlarmIndexes = Set(res?.alarmSchedule ?? []) - self.baseType = res?.baseType == ConvertDate.solar.title ? 1 : 0 - if let date = res?.baseDate { - self.baseDate = self.extractYearMonthDay(from: date)! - } - } - .store(in: &cancellables) - } - NotificationCenter.default.addObserver( - forName: UIResponder.keyboardWillShowNotification, - object: nil, - queue: .main - ) { noti in - guard let keyboardFrame = noti.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return } - keyboardHeight = keyboardFrame.height - } - NotificationCenter - .default - .addObserver( - forName: UIResponder.keyboardWillHideNotification, - object: nil, - queue: .main - ) { _ in - keyboardHeight = 0 - } - } - .onDisappear { - NotificationCenter - .default - .removeObserver( - self, - name: UIResponder.keyboardWillShowNotification, - object: nil - ) - NotificationCenter - .default - .removeObserver( - self, - name: UIResponder.keyboardWillHideNotification, - object: nil - ) - } + .onAppear(perform: actionsOnAppear) + .onDisappear(perform: actionOnDisappear) .onReceive(viewModel.viewDismissalModePublisher) { shouldDismiss in if shouldDismiss { withAnimation { @@ -255,6 +200,11 @@ struct CreationView: View { } } } + .onChange(of: showConfirmView) { _, showed in + if showed { + hideKeyboard() + } + } } .navigationBarHidden(true) } @@ -274,16 +224,11 @@ extension CreationView { private func extractYearMonthDay(from dateString: String) -> [Int]? { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" - guard let date = dateFormatter.date(from: dateString) else { - print("=== DEBUG: Invalid date format") - return nil - } - + let date = dateFormatter.date(from: dateString)! let calendar = Calendar.current let year = calendar.component(.year, from: date) % 100 let month = calendar.component(.month, from: date) let day = calendar.component(.day, from: date) - return [year, month, day] } @@ -301,13 +246,58 @@ extension CreationView { } private func randomCardType() -> String { - let cardType = [ - CardType.lunar.rawValue, - CardType.tail.rawValue, - CardType.arm.rawValue, - CardType.face.rawValue, - CardType.forest.rawValue - ] - return cardType.randomElement()! + return CardType.allCases.randomElement()!.rawValue + } + + private func actionsOnAppear() { + switch type { + case .create: + self.selectedAlarmIndexes = Set([AlarmPeriod.dDay.schedule]) + focusField = .eventName + case .edit: + viewModel.fetchAnniversaryDetail(id: id!) + viewModel.$anniversaryDetail + .receive(on: DispatchQueue.main) + .sink { res in + self.name = res?.title ?? "" + self.memo = res?.content ?? "" + self.selectedAlarmIndexes = Set(res?.alarmSchedule ?? []) + self.baseType = res?.baseType == ConvertDate.solar.title ? 1 : 0 + if let date = res?.baseDate { + self.baseDate = self.extractYearMonthDay(from: date)! + } + } + .store(in: &cancellables) + } + NotificationCenter.default.addObserver( + forName: UIResponder.keyboardWillShowNotification, + object: nil, + queue: .main + ) { noti in + guard let keyboardFrame = noti.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return } + keyboardHeight = keyboardFrame.height + } + NotificationCenter + .default + .addObserver( + forName: UIResponder.keyboardWillHideNotification, + object: nil, + queue: .main + ) { _ in + keyboardHeight = 0 + } + } + + private func actionOnDisappear() { + NotificationCenter.default.removeObserver( + self, + name: UIResponder.keyboardWillShowNotification, + object: nil + ) + NotificationCenter.default.removeObserver( + self, + name: UIResponder.keyboardWillHideNotification, + object: nil + ) } } diff --git a/DontForget/Sources/Presentations/Application/Creation/CreationViewModel.swift b/DontForget/Sources/Presentations/Application/Creation/CreationViewModel.swift index 38d9f0f..e656f25 100644 --- a/DontForget/Sources/Presentations/Application/Creation/CreationViewModel.swift +++ b/DontForget/Sources/Presentations/Application/Creation/CreationViewModel.swift @@ -22,11 +22,6 @@ final class CreationViewModel: ViewModelType { @Published var anniversaryDetail: AnniversaryDetailDTO? @Published var title: String? @Published var date: String? - @Published var content: String = "" - @Published var calendarType: String = "" - @Published var cardType: String = "" - @Published var alarmSchedule: [String] = [] - @Published var getDate: [Int] = [] var viewDismissalModePublisher = PassthroughSubject() private var dismiss = false { @@ -142,11 +137,7 @@ final class CreationViewModel: ViewModelType { } } .receive(on: DispatchQueue.main) - .sink { completion in - if case .failure = completion { - #warning("handling error") - } - } receiveValue: { [weak self] response in + .sink { _ in } receiveValue: { [weak self] response in if let response = response { self?.anniversaryDetail = response.anniversaryDetail self?.state = .success diff --git a/DontForget/Sources/Presentations/Application/Creation/Section/CustomDatePicker.swift b/DontForget/Sources/Presentations/Application/Creation/Section/CustomDatePicker.swift index 565ab6f..03a7e96 100644 --- a/DontForget/Sources/Presentations/Application/Creation/Section/CustomDatePicker.swift +++ b/DontForget/Sources/Presentations/Application/Creation/Section/CustomDatePicker.swift @@ -88,8 +88,7 @@ struct CustomDatePicker: View { Spacer(minLength: fullView.size.height / 2 - 25) LazyVGrid(columns: [GridItem(.fixed(32))], spacing: 0) { ForEach(values, id: \.self) { value in - let displayValue = value % 100 - Text("\(String(format: "%02d", displayValue))") + Text("\(String(format: "%02d", value % 100))") .font(.pretendard(size: 24)) .foregroundColor(selection.wrappedValue == value ? .blue : .gray) .frame(width: 60, height: 63) @@ -108,8 +107,7 @@ struct CustomDatePicker: View { } .onPreferenceChange(CenterPreferenceKey.self) { centers in DispatchQueue.main.async { - if !isProgrammaticScroll, - let closest = centers.min(by: { abs($0.value) < abs($1.value) }), + if !isProgrammaticScroll, let closest = centers.min(by: { abs($0.value) < abs($1.value) }), selection.wrappedValue != closest.key { withAnimation { scrollViewProxy.scrollTo(closest.key, anchor: .center) diff --git a/DontForget/Sources/Presentations/Application/Detail/View/AnniversaryDetailView.swift b/DontForget/Sources/Presentations/Application/Detail/View/AnniversaryDetailView.swift index 64bb230..44dc8a9 100644 --- a/DontForget/Sources/Presentations/Application/Detail/View/AnniversaryDetailView.swift +++ b/DontForget/Sources/Presentations/Application/Detail/View/AnniversaryDetailView.swift @@ -164,7 +164,7 @@ struct AnniversaryDetailView: View { coordinateSpace: .local ) .onEnded({ value in - if value.translation.width > 200 { + if value.translation.width > 100 { withAnimation { dismiss() } diff --git a/DontForget/Sources/Presentations/Application/Detail/ViewModel/AnniversaryDetailViewModel.swift b/DontForget/Sources/Presentations/Application/Detail/ViewModel/AnniversaryDetailViewModel.swift index 4f88428..f910755 100644 --- a/DontForget/Sources/Presentations/Application/Detail/ViewModel/AnniversaryDetailViewModel.swift +++ b/DontForget/Sources/Presentations/Application/Detail/ViewModel/AnniversaryDetailViewModel.swift @@ -13,7 +13,7 @@ final class DefaultAnniversaryDetailViewModel: ViewModelType { // MARK: - Properties let anniversaryId: Int private var cancellables = Set() - @Published var state: State + @Published var state: State = .idle @Published var anniversaryDetail: AnniversaryDetailDTO? private let anniversaryDetailRepository: AnniversaryDetailRepository private let deletionRepository: DeletionRepository @@ -44,7 +44,6 @@ final class DefaultAnniversaryDetailViewModel: ViewModelType { anniversaryDetailRepository: AnniversaryDetailRepository, deletionRepository: DeletionRepository ) { - self.state = .loading self.anniversaryId = anniversaryId self.anniversaryDetailRepository = anniversaryDetailRepository self.fetchAnniversaryDetailUseCase = DefaultFetchAnniversaryDetailUseCase( @@ -82,19 +81,13 @@ final class DefaultAnniversaryDetailViewModel: ViewModelType { } catch { print("=== DEBUG: \(error)") promise(.failure(error)) - self.state = .failed("failed fetchAnniversaryDetail()") } } } .receive(on: DispatchQueue.main) - .sink { completion in - if case .failure = completion { - #warning("handling error") - } - } receiveValue: { [weak self] response in + .sink { _ in } receiveValue: { [weak self] response in if let response = response { self?.anniversaryDetail = response.anniversaryDetail - self?.state = .success } } .store(in: &cancellables) @@ -119,8 +112,7 @@ final class DefaultAnniversaryDetailViewModel: ViewModelType { .sink { completion in self.dismiss = true if case .failure = completion { - #warning("handling error") - print(completion) + print("=== DEBUG: \(completion)") } } receiveValue: { _ in } .store(in: &cancellables) diff --git a/DontForget/Sources/Presentations/Application/Home/View/HomeView.swift b/DontForget/Sources/Presentations/Application/Home/View/HomeView.swift index 1e4aa1b..1a696e9 100644 --- a/DontForget/Sources/Presentations/Application/Home/View/HomeView.swift +++ b/DontForget/Sources/Presentations/Application/Home/View/HomeView.swift @@ -27,6 +27,7 @@ struct HomeView: View { @State private var navigateToCreationView = false @State private var isNavigate = false @State private var id = -1 + private var networkConnected: Bool { NetworkMonitor.shared.isConnected } var body: some View { NavigationView { @@ -81,7 +82,8 @@ struct HomeView: View { } .clipShape(RoundedRectangle(cornerRadius: 32)) .onTapGesture { - if let firstAnniversaryDetail = viewModel.firstAnniversaryDetail { + if let firstAnniversaryDetail = viewModel.firstAnniversaryDetail, + networkConnected { id = firstAnniversaryDetail.anniversaryId isNavigate = true } @@ -109,9 +111,11 @@ struct HomeView: View { .simultaneousGesture( TapGesture() .onEnded({ - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - id = anniversaries[index].anniversaryId - isNavigate = true + if networkConnected { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + id = anniversaries[index].anniversaryId + isNavigate = true + } } }) ) @@ -129,10 +133,7 @@ struct HomeView: View { #endif } .offset(y: anniversaries.isEmpty ? 0 : -140) - .onAppear { - viewModel.action(.readAnniversaries) - viewModel.action(.changePushState) - } + .onAppear(perform: actionOnAppear) .id(Self.scrollTopView) } .scrollDisabled(anniversaries.isEmpty) @@ -150,6 +151,20 @@ struct HomeView: View { } } } + .onChange(of: networkConnected) { _, status in + if status { + actionOnAppear() + } + } + .toolbar { + if !networkConnected { + ToolbarItem(placement: .status) { + Text("네트워크 연결이 없어요") + .font(.pretendard(size: 15)) + .foregroundStyle(.white) + } + } + } } } } @@ -204,5 +219,11 @@ extension HomeView { isActive: $navigateToCreationView, label: { AddNewAnniversaryView() } ) + .disabled(!networkConnected) + } + + private func actionOnAppear() { + viewModel.action(.readAnniversaries) + viewModel.action(.changePushState) } } diff --git a/DontForget/Sources/Presentations/Application/Home/ViewModel/HomeViewModel.swift b/DontForget/Sources/Presentations/Application/Home/ViewModel/HomeViewModel.swift index 29291bf..6015843 100644 --- a/DontForget/Sources/Presentations/Application/Home/ViewModel/HomeViewModel.swift +++ b/DontForget/Sources/Presentations/Application/Home/ViewModel/HomeViewModel.swift @@ -79,11 +79,7 @@ final class DefaultHomeViewModel: ViewModelType { } } .receive(on: DispatchQueue.main) - .sink { completion in - if case .failure = completion { - #warning("handling error") - } - } receiveValue: { [weak self] response in + .sink { _ in } receiveValue: { [weak self] response in if let self = self, let response = response { self.anniversaries = response.anniversaries.sorted(by: { $0.solarDate < $1.solarDate }) print("=== DEBUG: \(self.anniversaries)") @@ -117,11 +113,7 @@ final class DefaultHomeViewModel: ViewModelType { } } .receive(on: DispatchQueue.main) - .sink { completion in - if case .failure = completion { - #warning("handling error") - } - } receiveValue: { [weak self] response in + .sink { _ in } receiveValue: { [weak self] response in if let response = response { self?.firstAnniversaryDetail = response.anniversaryDetail self?.state = .success @@ -154,13 +146,7 @@ final class DefaultHomeViewModel: ViewModelType { } } .receive(on: DispatchQueue.main) - .sink { completion in - if case .failure = completion { - #warning("handling error") - } - } receiveValue: { response in - print("=== DEBUG: fcm 발송 테스트 \(response)") - } + .sink { _ in } receiveValue: { _ in } .store(in: &cancellables) } @@ -177,11 +163,7 @@ final class DefaultHomeViewModel: ViewModelType { } } .receive(on: DispatchQueue.main) - .sink { completion in - if case .failure = completion { - #warning("handling error") - } - } receiveValue: { response in + .sink { _ in } receiveValue: { response in print("=== DEBUG: fcmTest \(response)") } .store(in: &cancellables)