diff --git a/App/Moda/Info.plist b/App/Moda/Info.plist index 118ae21..bacae88 100644 --- a/App/Moda/Info.plist +++ b/App/Moda/Info.plist @@ -11,9 +11,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.0 + 1.5.0 CFBundleVersion - 1.4.0.1 + 1.5.0.3 GoogleServiceFileName $(ENV_GOOGLE_INFO_PLIST) ITSAppUsesNonExemptEncryption diff --git a/App/Moda/Sources/Feature/Home/Components/DailyMemo/HomeDailyMemoCore.swift b/App/Moda/Sources/Feature/Home/Components/DailyMemo/HomeDailyMemoCore.swift index bb37237..15b2ed6 100644 --- a/App/Moda/Sources/Feature/Home/Components/DailyMemo/HomeDailyMemoCore.swift +++ b/App/Moda/Sources/Feature/Home/Components/DailyMemo/HomeDailyMemoCore.swift @@ -16,6 +16,7 @@ struct HomeDailyMemoCore: Reducer { struct State: Equatable { var currentDate: Date var isFolded: Bool = true + var memosUpdatingTrigger: Int = 0 var memos: [DailyMemo] = [] } @@ -24,6 +25,7 @@ struct HomeDailyMemoCore: Reducer { case view(View) enum View: BindableAction { case binding(BindingAction) + case onFirstAppear case optionTapped(DailyMemo) } @@ -46,6 +48,9 @@ struct HomeDailyMemoCore: Reducer { // From DailyMemoEditBottomSheet case updateMemo(DailyMemo) + + // From DailyMemoCategory + case reorderMemos } } @@ -72,6 +77,12 @@ struct HomeDailyMemoCore: Reducer { case let .view(viewAction): switch viewAction { + case .onFirstAppear: + if userData.firstMemoShown.value { + state.isFolded = false + userData.firstMemoShown.update(false) + } + return .none case let .optionTapped(memo): return .send(.delegate(.showMemoOption(memo))) @@ -85,7 +96,9 @@ struct HomeDailyMemoCore: Reducer { return .send(.view(.binding(.set(\.currentDate, date)))) case let .addMemo(memo): - state.memos.append(memo) + var updated = state.memos + updated.append(memo) + state.memos = updated.orderingByCategory().reordering() return .send(.delegate(.memoUpdated(state.memos))) case let .updateMemo(memo): @@ -107,6 +120,10 @@ struct HomeDailyMemoCore: Reducer { await toast.show(.init(icon: .icDelete, message: "삭제 완료!")) } ) + + case .reorderMemos: + state.memosUpdatingTrigger += 1 + return .none } default: diff --git a/App/Moda/Sources/Feature/Home/Components/DailyMemo/HomeDailyMemoView.swift b/App/Moda/Sources/Feature/Home/Components/DailyMemo/HomeDailyMemoView.swift index 1c2aca9..20a00b4 100644 --- a/App/Moda/Sources/Feature/Home/Components/DailyMemo/HomeDailyMemoView.swift +++ b/App/Moda/Sources/Feature/Home/Components/DailyMemo/HomeDailyMemoView.swift @@ -15,7 +15,6 @@ struct HomeDailyMemoView: View { @Bindable var store: StoreOf - @State var skipUpdate: Bool = true @Environment(\.modelContext) var modelContext @Query var memosEntities: [DailyMemosEntity] @Query var memoCategoryEntities: [DailyMemoCategoryEntity] @@ -23,27 +22,24 @@ struct HomeDailyMemoView: View { var body: some View { content .onFirstAppear { - let id = store.currentDate.format(.yyMMdd) - let memos = memosEntities.first(where: { $0.id == id })?.memos.toDomain ?? [] - send(.binding(.set(\.memos, memos))) + send(.onFirstAppear) } .onChange(of: store.currentDate, initial: true) { - skipUpdate = true change(by: $1) } - .onChange(of: store.memos, initial: true) { - if skipUpdate { - skipUpdate = false - return - } + .onChange(of: store.memos) { update(memos: $1) } + .onChange(of: store.memosUpdatingTrigger) { + change(by: store.currentDate) + } } private func change(by currentDate: Date) { let id = currentDate.format(.yyMMdd) - let memos = memosEntities.first(where: { $0.id == id })?.memos.toDomain ?? [] - send(.binding(.set(\.memos, memos))) + let memos = memosEntities.first(where: { $0.id == id })?.memos.toDomain + let ordered = memos?.orderingByCategory() ?? [] + send(.binding(.set(\.memos, ordered))) } private func update(memos: [DailyMemo]) { @@ -160,3 +156,40 @@ private extension HomeDailyMemoView { } } } + +#Preview { + HomeDailyMemoView( + store: .init(initialState: HomeDailyMemoCore.State(currentDate: .today)) { + HomeDailyMemoCore() + } + ) + .modelContainer(previewContainer) +} + +@MainActor +let previewContainer: ModelContainer = { + do { + let container = try ModelContainer(for: DailyMemosEntity.self, DailyMemoCategoryEntity.self, + configurations: .init(isStoredInMemoryOnly: true)) + + let entity = DailyMemoCategoryEntity( + id: "123", + order: 0, + usesCount: 0, + isAdded: true, + emoji: .Affirmation, + name: "123123" + ) + container.mainContext.insert(entity) + container.mainContext.insert( + DailyMemosEntity(id: Date.today.format(.yyMMdd), memos: [ + DailyMemoEntity(id: "123", order: 0, content: "123123", category: entity) + ]) + ) + try container.mainContext.save() + + return container + } catch { + fatalError("Failed to create container") + } +}() diff --git a/App/Moda/Sources/Feature/Home/Components/Selector/HomeDateSelectorView.swift b/App/Moda/Sources/Feature/Home/Components/Selector/HomeDateSelectorView.swift index a35e122..223e3f6 100644 --- a/App/Moda/Sources/Feature/Home/Components/Selector/HomeDateSelectorView.swift +++ b/App/Moda/Sources/Feature/Home/Components/Selector/HomeDateSelectorView.swift @@ -58,6 +58,8 @@ struct HomeDateSelectorView: View { if let idx = weekDates[1].firstIndex(where: { $0.date == new.date }) { weekDates[1][idx] = new } + } else { + initialize() } } } @@ -86,3 +88,18 @@ struct HomeDateSelectorView: View { } } } + +#Preview { + @Previewable @State var date: HomeDate = .today + @Previewable @State var weekDates: [[HomeDate]] = [ + DateManager.shared.weekDates(for: .today.addDays(-7)), + DateManager.shared.weekDates(for: .today), + DateManager.shared.weekDates(for: .today.addDays(7)) + ] + + HomeDateSelectorView( + currentDate: $date, + weekDates: $weekDates, + selection: 1 + ) +} diff --git a/App/Moda/Sources/Feature/Home/HomeCore.swift b/App/Moda/Sources/Feature/Home/HomeCore.swift index 236e176..f1b726c 100644 --- a/App/Moda/Sources/Feature/Home/HomeCore.swift +++ b/App/Moda/Sources/Feature/Home/HomeCore.swift @@ -304,7 +304,9 @@ struct HomeCore: Reducer { // MARK: Child - Routine case let .memo(.delegate(.memoUpdated(memos))): - state.currentDate.hasMemo = !memos.isEmpty + if state.currentDate.hasMemo != !memos.isEmpty { + state.currentDate.hasMemo = !memos.isEmpty + } return .none default: diff --git a/App/Moda/Sources/Feature/MainTab/MainTabCore.swift b/App/Moda/Sources/Feature/MainTab/MainTabCore.swift index c20830d..7abeb23 100644 --- a/App/Moda/Sources/Feature/MainTab/MainTabCore.swift +++ b/App/Moda/Sources/Feature/MainTab/MainTabCore.swift @@ -258,7 +258,7 @@ struct MainTabCore: Reducer { case .delegate(.close): state.updateMemoAddTrigger += 1 state.dailyMemoEdit = nil - return .none + return .send(.home(.memo(.passThrough(.reorderMemos)))) default: return .none diff --git a/App/Moda/Sources/Feature/Splash/SplashCore.swift b/App/Moda/Sources/Feature/Splash/SplashCore.swift index 87871ca..3057445 100644 --- a/App/Moda/Sources/Feature/Splash/SplashCore.swift +++ b/App/Moda/Sources/Feature/Splash/SplashCore.swift @@ -24,6 +24,7 @@ struct SplashCore: Reducer { var routineRecords: [RoutineRecord] = [] // Memo + var memos: [DailyMemo] = [] var memoCategories: [DailyMemoCategory] = [] } @@ -57,6 +58,11 @@ struct SplashCore: Reducer { } if userData.firstRoutineMemo.value { + state.memos = [ + .init(id: "0", order: 0, content: "오늘 하루는 컨디션이 너무 아쉬웠다.. 요즘에 커피를 많이 마셔서 그런듯 🥹 앞으로는 디카페인만 마시면서 당분간은 컨디션 회복이 제일 우선일듯 하다!", category: .writing), + .init(id: "1", order: 1, content: "오늘 기분 쏘쏘~", category: .mood), + .init(id: "2", order: 2, content: "점심 : 김치찌개\n저녁 : 직접 만든 참치 포케", category: .diet) + ] state.memoCategories = .guideline userData.firstRoutineMemo.update(false) } diff --git a/App/Moda/Sources/Feature/Splash/SplashView.swift b/App/Moda/Sources/Feature/Splash/SplashView.swift index dffaed0..250e00a 100644 --- a/App/Moda/Sources/Feature/Splash/SplashView.swift +++ b/App/Moda/Sources/Feature/Splash/SplashView.swift @@ -19,8 +19,12 @@ struct SplashView: View { @Query var dailyTodosList: [DailyTodos] @Query var routines: [RoutineEntity] @Query var routineRecords: [RoutineRecordEntity] + @Query var memosEntities: [DailyMemosEntity] @Query var memoCategories: [DailyMemoCategoryEntity] + @State var isMemoCategoriesStored: Bool = false + @State var tmpMemoCategories: [DailyMemoCategoryEntity] = [] + public init(store: StoreOf) { self.store = store } @@ -61,8 +65,22 @@ struct SplashView: View { .onChange(of: store.memoCategories) { guard memoCategories.count == 0 else { return } $1.forEach { - modelContext.insert($0.toEntity) + let entity = $0.toEntity + modelContext.insert(entity) + tmpMemoCategories.append(entity) + } + try? modelContext.save() + isMemoCategoriesStored = true + } + .onChange(of: isMemoCategoriesStored) { _, _ in + guard memosEntities.count == 0 else { return } + var memoEntities: [DailyMemoEntity] = [] + store.memos.forEach { memo in + if let category = tmpMemoCategories.first(where: { $0.id == memo.category.id }) { + memoEntities.append(DailyMemoEntity(id: memo.id, order: memo.order, content: memo.content, category: category)) + } } + modelContext.insert(DailyMemosEntity(id: Date.today.format(.yyMMdd), memos: memoEntities)) try? modelContext.save() } } diff --git a/App/Moda/Sources/Util/UserData.swift b/App/Moda/Sources/Util/UserData.swift index 6bafb4c..cf68eb7 100644 --- a/App/Moda/Sources/Util/UserData.swift +++ b/App/Moda/Sources/Util/UserData.swift @@ -34,6 +34,7 @@ enum UserDataKey: String, CaseIterable { // 1.5.0 case firstRoutineMemo case hasBeenShownMemoGuideline + case firstMemoShown } public final class UserData { @@ -53,6 +54,7 @@ public final class UserData { public let firstRoutineUsage = DataStorage(key: .firstRoutineUsage, defaultValue: true) public let firstRoutineMemo = DataStorage(key: .firstRoutineMemo, defaultValue: true) public let hasBeenShownMemoGuideline = DataStorage(key: .hasBeenShownMemoGuideline, defaultValue: false) + public let firstMemoShown = DataStorage(key: .firstMemoShown, defaultValue: true) } public extension UserData { diff --git a/App/ModaWidget/Info.plist b/App/ModaWidget/Info.plist index ac5f969..96fd114 100644 --- a/App/ModaWidget/Info.plist +++ b/App/ModaWidget/Info.plist @@ -13,9 +13,9 @@ CFBundleName ModaWidget CFBundleShortVersionString - 1.4.0 + 1.5.0 CFBundleVersion - 1.4.0.1 + 1.5.0.3 NSExtension NSExtensionPointIdentifier diff --git a/Core/ModaData/Sources/Entity/DailyMemoCategoryEntity.swift b/Core/ModaData/Sources/Entity/DailyMemoCategoryEntity.swift index 151e1b2..aefdaa2 100644 --- a/Core/ModaData/Sources/Entity/DailyMemoCategoryEntity.swift +++ b/Core/ModaData/Sources/Entity/DailyMemoCategoryEntity.swift @@ -13,6 +13,7 @@ import Foundation public class DailyMemoCategoryEntity: Identifiable, Equatable, Hashable { @Attribute(.unique) public var id: String + // priority public var order: Int public var usesCount: Int public var isAdded: Bool diff --git a/Core/ModaData/Sources/Model/Bookmark.swift b/Core/ModaData/Sources/Model/Bookmark.swift index 799aa9d..bcd22f0 100644 --- a/Core/ModaData/Sources/Model/Bookmark.swift +++ b/Core/ModaData/Sources/Model/Bookmark.swift @@ -28,7 +28,7 @@ public struct Bookmark: Identifiable, Equatable, Hashable { public init(title: String, isFolded: Bool = false, todos: [BookmarkTodo] = []) { self.id = Self.uniqueId - self.order = -1 + self.order = Int.max self.title = title self.isFolded = isFolded self.todos = todos diff --git a/Core/ModaData/Sources/Model/DailyMemo.swift b/Core/ModaData/Sources/Model/DailyMemo.swift index 6053d3b..bd6b5ce 100644 --- a/Core/ModaData/Sources/Model/DailyMemo.swift +++ b/Core/ModaData/Sources/Model/DailyMemo.swift @@ -14,7 +14,7 @@ public struct DailyMemo: Identifiable, Equatable, Hashable, Orderable { public var content: String public var category: DailyMemoCategory - init(id: String, order: Int, content: String, category: DailyMemoCategory) { + public init(id: String, order: Int, content: String, category: DailyMemoCategory) { self.id = id self.order = order self.content = content @@ -23,10 +23,21 @@ public struct DailyMemo: Identifiable, Equatable, Hashable, Orderable { public init(content: String, category: DailyMemoCategory) { self.id = Self.uniqueId - self.order = -1 + self.order = Int.max self.content = content self.category = category } public static var uniqueId: String { String(Int(Date().timeIntervalSince1970)) } } + +public extension [DailyMemo] { + func orderingByCategory() -> Self { + self.sorted { + if $0.category.order == $1.category.order { + return $0.order < $1.order + } + return $0.category.order < $1.category.order + } + } +} diff --git a/Core/ModaData/Sources/Model/DailyMemoCategory.swift b/Core/ModaData/Sources/Model/DailyMemoCategory.swift index c38e679..b325790 100644 --- a/Core/ModaData/Sources/Model/DailyMemoCategory.swift +++ b/Core/ModaData/Sources/Model/DailyMemoCategory.swift @@ -27,7 +27,7 @@ public struct DailyMemoCategory: Identifiable, Equatable, Hashable, Orderable { public init(emoji: Emoji, name: String) { self.id = Self.uniqueId - self.order = -1 + self.order = Int.max self.usesCount = 0 self.isAdded = true self.emoji = emoji diff --git a/Core/ModaData/Sources/Model/Routine.swift b/Core/ModaData/Sources/Model/Routine.swift index 2cd8531..1b65ba7 100644 --- a/Core/ModaData/Sources/Model/Routine.swift +++ b/Core/ModaData/Sources/Model/Routine.swift @@ -27,7 +27,7 @@ public struct Routine: Identifiable, Equatable { public init(emoji: Emoji, name: String, duration: RoutineDuration = .weekday(.init(Weekday.allCases))) { self.id = name + Self.uniqueId - self.order = -1 + self.order = Int.max self.emoji = emoji self.name = name self.duration = duration