fix issue with two votes on the same date
fix issue with header not showing correct vote date split logic for Persistence into different files create class that deals with voting time, existing votes, and what should be shown based on that
This commit is contained in:
@@ -27,6 +27,16 @@
|
||||
1C358FBE27B4D1F2002C83A6 /* CurrentStreakTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C358FBD27B4D1F2002C83A6 /* CurrentStreakTemplate.swift */; };
|
||||
1C358FC027B4D20C002C83A6 /* MonthTotalTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C358FBF27B4D20C002C83A6 /* MonthTotalTemplate.swift */; };
|
||||
1C358FC227B4D227002C83A6 /* WeekTotalTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C358FC127B4D227002C83A6 /* WeekTotalTemplate.swift */; };
|
||||
1C4FF3BB27BEDDF000BE8F34 /* ShowBasedOnVoteLogics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4FF3BA27BEDDF000BE8F34 /* ShowBasedOnVoteLogics.swift */; };
|
||||
1C4FF3BC27BEDF6600BE8F34 /* ShowBasedOnVoteLogics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4FF3BA27BEDDF000BE8F34 /* ShowBasedOnVoteLogics.swift */; };
|
||||
1C4FF3BE27BEDF9100BE8F34 /* PersistenceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4FF3BD27BEDF9100BE8F34 /* PersistenceHelper.swift */; };
|
||||
1C4FF3C027BEE06900BE8F34 /* PersistenceGET.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4FF3BF27BEE06900BE8F34 /* PersistenceGET.swift */; };
|
||||
1C4FF3C127BEE06900BE8F34 /* PersistenceGET.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4FF3BF27BEE06900BE8F34 /* PersistenceGET.swift */; };
|
||||
1C4FF3C327BEE07200BE8F34 /* PersistenceDELETE.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4FF3C227BEE07200BE8F34 /* PersistenceDELETE.swift */; };
|
||||
1C4FF3C427BEE07200BE8F34 /* PersistenceDELETE.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4FF3C227BEE07200BE8F34 /* PersistenceDELETE.swift */; };
|
||||
1C4FF3C727BEE09E00BE8F34 /* PersistenceADD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4FF3C627BEE09E00BE8F34 /* PersistenceADD.swift */; };
|
||||
1C4FF3C827BEE09E00BE8F34 /* PersistenceADD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4FF3C627BEE09E00BE8F34 /* PersistenceADD.swift */; };
|
||||
1C4FF3C927BEE0C300BE8F34 /* PersistenceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4FF3BD27BEDF9100BE8F34 /* PersistenceHelper.swift */; };
|
||||
1C5F4976279C84090092F1B4 /* OnboardingData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C5F4975279C84090092F1B4 /* OnboardingData.swift */; };
|
||||
1C5F4978279C945E0092F1B4 /* UserDefaultsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C5F4977279C945E0092F1B4 /* UserDefaultsStore.swift */; };
|
||||
1C683FCA2792281400745862 /* Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C683FC92792281400745862 /* Stats.swift */; };
|
||||
@@ -149,6 +159,11 @@
|
||||
1C358FBD27B4D1F2002C83A6 /* CurrentStreakTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentStreakTemplate.swift; sourceTree = "<group>"; };
|
||||
1C358FBF27B4D20C002C83A6 /* MonthTotalTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthTotalTemplate.swift; sourceTree = "<group>"; };
|
||||
1C358FC127B4D227002C83A6 /* WeekTotalTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeekTotalTemplate.swift; sourceTree = "<group>"; };
|
||||
1C4FF3BA27BEDDF000BE8F34 /* ShowBasedOnVoteLogics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowBasedOnVoteLogics.swift; sourceTree = "<group>"; };
|
||||
1C4FF3BD27BEDF9100BE8F34 /* PersistenceHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceHelper.swift; sourceTree = "<group>"; };
|
||||
1C4FF3BF27BEE06900BE8F34 /* PersistenceGET.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceGET.swift; sourceTree = "<group>"; };
|
||||
1C4FF3C227BEE07200BE8F34 /* PersistenceDELETE.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceDELETE.swift; sourceTree = "<group>"; };
|
||||
1C4FF3C627BEE09E00BE8F34 /* PersistenceADD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceADD.swift; sourceTree = "<group>"; };
|
||||
1C5F4975279C84090092F1B4 /* OnboardingData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingData.swift; sourceTree = "<group>"; };
|
||||
1C5F4977279C945E0092F1B4 /* UserDefaultsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsStore.swift; sourceTree = "<group>"; };
|
||||
1C683FC92792281400745862 /* Stats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stats.swift; sourceTree = "<group>"; };
|
||||
@@ -275,6 +290,18 @@
|
||||
path = SharingTemplates;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1C4FF3C527BEE07800BE8F34 /* Persisence */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1CD90AEF278C7DDF001C4FEA /* Persistence.swift */,
|
||||
1C4FF3C627BEE09E00BE8F34 /* PersistenceADD.swift */,
|
||||
1C4FF3C227BEE07200BE8F34 /* PersistenceDELETE.swift */,
|
||||
1C4FF3BF27BEE06900BE8F34 /* PersistenceGET.swift */,
|
||||
1C4FF3BD27BEDF9100BE8F34 /* PersistenceHelper.swift */,
|
||||
);
|
||||
path = Persisence;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1CA03771279A291F00D26164 /* Onboarding */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -345,9 +372,10 @@
|
||||
1C744F2B278CE15600953A57 /* AppDelegate.swift */,
|
||||
1CC469A9278F30A0003E0C6E /* BGTask.swift */,
|
||||
1CD90B75278C8119001C4FEA /* LocalNotification.swift */,
|
||||
1CD90AEF278C7DDF001C4FEA /* Persistence.swift */,
|
||||
1C4FF3C527BEE07800BE8F34 /* Persisence */,
|
||||
1CD90B5C278C7EAD001C4FEA /* Random.swift */,
|
||||
1C683FC92792281400745862 /* Stats.swift */,
|
||||
1C4FF3BA27BEDDF000BE8F34 /* ShowBasedOnVoteLogics.swift */,
|
||||
1CA03771279A291F00D26164 /* Onboarding */,
|
||||
1C26190127960CDA00FDC148 /* Protocols */,
|
||||
1CAD602A27A5C1C800C520BD /* Views */,
|
||||
@@ -639,6 +667,7 @@
|
||||
1CAD603C27A5C1C800C520BD /* HeaderStatsView.swift in Sources */,
|
||||
1CAD603827A5C1C800C520BD /* AddMoodHeaderView.swift in Sources */,
|
||||
1CA0377C279B605000D26164 /* OnboardingWrapup.swift in Sources */,
|
||||
1C4FF3C327BEE07200BE8F34 /* PersistenceDELETE.swift in Sources */,
|
||||
1CA03775279A294800D26164 /* OnboardingDay.swift in Sources */,
|
||||
1CAD603727A5C1C800C520BD /* FilterView.swift in Sources */,
|
||||
1C683FCA2792281400745862 /* Stats.swift in Sources */,
|
||||
@@ -646,6 +675,7 @@
|
||||
1CD90B76278C8119001C4FEA /* LocalNotification.swift in Sources */,
|
||||
1C358FB627B0AE15002C83A6 /* AllMoodsTotalTemplate.swift in Sources */,
|
||||
1CD90B16278C7DE0001C4FEA /* Feels.xcdatamodeld in Sources */,
|
||||
1C4FF3BE27BEDF9100BE8F34 /* PersistenceHelper.swift in Sources */,
|
||||
1CC469AA278F30A0003E0C6E /* BGTask.swift in Sources */,
|
||||
1CAD603B27A5C1C800C520BD /* ContentView.swift in Sources */,
|
||||
1C5F4976279C84090092F1B4 /* OnboardingData.swift in Sources */,
|
||||
@@ -658,6 +688,8 @@
|
||||
1C744F2C278CE15600953A57 /* AppDelegate.swift in Sources */,
|
||||
1CD90B63278C7EBA001C4FEA /* Mood.swift in Sources */,
|
||||
1C358FBE27B4D1F2002C83A6 /* CurrentStreakTemplate.swift in Sources */,
|
||||
1C4FF3C727BEE09E00BE8F34 /* PersistenceADD.swift in Sources */,
|
||||
1C4FF3BB27BEDDF000BE8F34 /* ShowBasedOnVoteLogics.swift in Sources */,
|
||||
1CAD603527A5C1C800C520BD /* SettingsView.swift in Sources */,
|
||||
1CD90B53278C7E7A001C4FEA /* FeelsWidget.intentdefinition in Sources */,
|
||||
1CC469AC27907D48003E0C6E /* DayChartView.swift in Sources */,
|
||||
@@ -677,6 +709,7 @@
|
||||
1CD90B18278C7DE0001C4FEA /* FeelsApp.swift in Sources */,
|
||||
1C358FC027B4D20C002C83A6 /* MonthTotalTemplate.swift in Sources */,
|
||||
1CA03777279A295600D26164 /* OnboardingTitle.swift in Sources */,
|
||||
1C4FF3C027BEE06900BE8F34 /* PersistenceGET.swift in Sources */,
|
||||
1CEC967127B9C2BB00CC8688 /* CustomIcon.swift in Sources */,
|
||||
1C358FAD27ADD0C3002C83A6 /* Theme.swift in Sources */,
|
||||
1C02589C27B9677A00EB91AC /* CreateIconView.swift in Sources */,
|
||||
@@ -722,6 +755,8 @@
|
||||
files = (
|
||||
1CD90B65278C7EBA001C4FEA /* Mood.swift in Sources */,
|
||||
1CEC967227B9C9FB00CC8688 /* IconView.swift in Sources */,
|
||||
1C4FF3BC27BEDF6600BE8F34 /* ShowBasedOnVoteLogics.swift in Sources */,
|
||||
1C4FF3C927BEE0C300BE8F34 /* PersistenceHelper.swift in Sources */,
|
||||
1CA2662D2793908700C0E12C /* Persistence.swift in Sources */,
|
||||
1CD90B5F278C7EAD001C4FEA /* Random.swift in Sources */,
|
||||
1CD90B68278C7EBA001C4FEA /* MoodEntryExtension.swift in Sources */,
|
||||
@@ -731,6 +766,9 @@
|
||||
1C683FCB2792281400745862 /* Stats.swift in Sources */,
|
||||
1CEC967327B9CA0C00CC8688 /* CustomIcon.swift in Sources */,
|
||||
1C10E25027A1AB220047948B /* OnboardingDay.swift in Sources */,
|
||||
1C4FF3C427BEE07200BE8F34 /* PersistenceDELETE.swift in Sources */,
|
||||
1C4FF3C827BEE09E00BE8F34 /* PersistenceADD.swift in Sources */,
|
||||
1C4FF3C127BEE06900BE8F34 /* PersistenceGET.swift in Sources */,
|
||||
1C10E24F27A1AB1D0047948B /* OnboardingData.swift in Sources */,
|
||||
1CD90B52278C7E7A001C4FEA /* FeelsWidget.intentdefinition in Sources */,
|
||||
1CD90B4D278C7E7A001C4FEA /* FeelsWidget.swift in Sources */,
|
||||
|
||||
@@ -36,6 +36,8 @@ struct FeelsApp: App {
|
||||
|
||||
if phase == .active {
|
||||
UIApplication.shared.applicationIconBadgeNumber = 0
|
||||
print("UserDefaultsStore input day", UserDefaultsStore.getOnboarding().inputDay)
|
||||
print("UserDefaultsStore input date", UserDefaultsStore.getOnboarding().date)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,51 +47,12 @@ class ContentModeViewModel: ObservableObject {
|
||||
grouped = PersistenceController.shared.splitIntoYearMonth()
|
||||
numberOfItems = numberOfEntries
|
||||
}
|
||||
|
||||
public func shouldShowVotingHeader() -> Bool {
|
||||
if isMissingCurrentVote() {
|
||||
return true
|
||||
}
|
||||
|
||||
return savedOnboardingData.ableToVoteBasedOnCurentTime() ? true : false
|
||||
}
|
||||
|
||||
|
||||
public func updateOnboardingData(onboardingData: OnboardingData) {
|
||||
self.savedOnboardingData = UserDefaultsStore.saveOnboarding(onboardingData: onboardingData)
|
||||
LocalNotification.scheduleReminder(atTime: onboardingData.date, withTitle: onboardingData.title)
|
||||
}
|
||||
|
||||
private func isMissingCurrentVote() -> Bool {
|
||||
let latestVoteUnLocked = UserDefaultsStore.getOnboarding().ableToVoteBasedOnCurentTime()
|
||||
let inputDay = UserDefaultsStore.getOnboarding().inputDay
|
||||
|
||||
var startDate: Date?
|
||||
|
||||
switch (latestVoteUnLocked, inputDay) {
|
||||
case (true, .Previous):
|
||||
startDate = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
|
||||
case (true, .Today):
|
||||
startDate = Date()
|
||||
case (false, .Previous):
|
||||
startDate = Calendar.current.date(byAdding: .day, value: -2, to: Date())!
|
||||
case (false, .Today):
|
||||
startDate = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
|
||||
}
|
||||
|
||||
startDate = Calendar.current.startOfDay(for: startDate!)
|
||||
let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate!)!
|
||||
|
||||
let fetchRequest = NSFetchRequest<MoodEntry>(entityName: "MoodEntry")
|
||||
let fromPredicate = NSPredicate(format: "%@ <= %K", startDate!
|
||||
as NSDate, #keyPath(MoodEntry.forDate))
|
||||
let toPredicate = NSPredicate(format: "%K < %@", #keyPath(MoodEntry.forDate), endDate as NSDate)
|
||||
let datePredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [fromPredicate, toPredicate])
|
||||
fetchRequest.predicate = datePredicate
|
||||
let entries = try! PersistenceController.shared.viewContext.count(for: fetchRequest)
|
||||
|
||||
return entries == 0
|
||||
}
|
||||
|
||||
public func updateData() {
|
||||
getGroupedData()
|
||||
}
|
||||
|
||||
104
Shared/Persisence/Persistence.swift
Normal file
104
Shared/Persisence/Persistence.swift
Normal file
@@ -0,0 +1,104 @@
|
||||
//
|
||||
// Persistence.swift
|
||||
// Shared
|
||||
//
|
||||
// Created by Trey Tartt on 1/5/22.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import SwiftUI
|
||||
|
||||
class PersistenceController {
|
||||
@AppStorage(UserDefaultsStore.Keys.useCloudKit.rawValue, store: GroupUserDefaults.groupDefaults) private var useCloudKit = false
|
||||
|
||||
static let shared = PersistenceController.persistenceController
|
||||
|
||||
private static var persistenceController: PersistenceController {
|
||||
return PersistenceController(inMemory: false)
|
||||
}
|
||||
|
||||
public var viewContext: NSManagedObjectContext {
|
||||
return PersistenceController.shared.container.viewContext
|
||||
}
|
||||
|
||||
public var switchContainerListeners = [(() -> Void)]()
|
||||
|
||||
public var earliestEntry: MoodEntry? {
|
||||
let fetchRequest = NSFetchRequest<MoodEntry>(entityName: "MoodEntry")
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: true)]
|
||||
let first = try! viewContext.fetch(fetchRequest).first
|
||||
return first ?? nil
|
||||
}
|
||||
|
||||
public var latestEntry: MoodEntry? {
|
||||
let fetchRequest = NSFetchRequest<MoodEntry>(entityName: "MoodEntry")
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: true)]
|
||||
let last = try! viewContext.fetch(fetchRequest).last
|
||||
return last ?? nil
|
||||
}
|
||||
|
||||
lazy var container: NSPersistentContainer = {
|
||||
setupContainer()
|
||||
}()
|
||||
|
||||
func switchContainer() {
|
||||
try? viewContext.save()
|
||||
container = setupContainer()
|
||||
for item in switchContainerListeners {
|
||||
item()
|
||||
}
|
||||
}
|
||||
|
||||
private func setupContainer() -> NSPersistentContainer {
|
||||
if useCloudKit {
|
||||
container = NSPersistentCloudKitContainer(name: "Feels")
|
||||
} else {
|
||||
container = NSCustomPersistentContainer(name: "Feels")
|
||||
}
|
||||
|
||||
for description in container.persistentStoreDescriptions {
|
||||
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
|
||||
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
|
||||
description.setOption(true as NSNumber, forKey: NSMigratePersistentStoresAutomaticallyOption)
|
||||
description.setOption(true as NSNumber, forKey: NSInferMappingModelAutomaticallyOption)
|
||||
}
|
||||
|
||||
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
|
||||
self.container.viewContext.automaticallyMergesChangesFromParent = true
|
||||
|
||||
if let error = error as NSError? {
|
||||
fatalError("Unresolved error \(error), \(error.userInfo)")
|
||||
}
|
||||
})
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
init(inMemory: Bool = false) {
|
||||
container = setupContainer()
|
||||
}
|
||||
}
|
||||
|
||||
extension NSManagedObjectContext {
|
||||
/// Executes the given `NSBatchDeleteRequest` and directly merges the changes to bring the given managed object context up to date.
|
||||
///
|
||||
/// - Parameter batchDeleteRequest: The `NSBatchDeleteRequest` to execute.
|
||||
/// - Throws: An error if anything went wrong executing the batch deletion.
|
||||
public func executeAndMergeChanges(using batchDeleteRequest: NSBatchDeleteRequest) throws {
|
||||
batchDeleteRequest.resultType = .resultTypeObjectIDs
|
||||
let result = try execute(batchDeleteRequest) as? NSBatchDeleteResult
|
||||
let changes: [AnyHashable: Any] = [NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? []]
|
||||
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [self])
|
||||
}
|
||||
}
|
||||
|
||||
class NSCustomPersistentContainer: NSPersistentContainer {
|
||||
override open class func defaultDirectoryURL() -> URL {
|
||||
var storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.groupShareId)
|
||||
storeURL = storeURL?.appendingPathComponent("Feels.sqlite")
|
||||
#if DEBUG
|
||||
storeURL = storeURL?.appendingPathComponent("Feels-Debug.sqlite")
|
||||
#endif
|
||||
return storeURL!
|
||||
}
|
||||
}
|
||||
63
Shared/Persisence/PersistenceADD.swift
Normal file
63
Shared/Persisence/PersistenceADD.swift
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// PersistenceADD.swift
|
||||
// Feels
|
||||
//
|
||||
// Created by Trey Tartt on 2/17/22.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
extension PersistenceController {
|
||||
public func add(mood: Mood, forDate date: Date, entryType: EntryType) {
|
||||
if let existingEntry = getEntry(byDate: date) {
|
||||
viewContext.delete(existingEntry)
|
||||
try? viewContext.save()
|
||||
}
|
||||
|
||||
let newItem = MoodEntry(context: viewContext)
|
||||
newItem.timestamp = Date()
|
||||
newItem.moodValue = Int16(mood.rawValue)
|
||||
newItem.forDate = date
|
||||
newItem.weekDay = Int16(Calendar.current.component(.weekday, from: date))
|
||||
newItem.canEdit = true
|
||||
newItem.canDelete = true
|
||||
newItem.entryType = Int16(entryType.rawValue)
|
||||
|
||||
do {
|
||||
try viewContext.save()
|
||||
} catch {
|
||||
let nsError = error as NSError
|
||||
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
|
||||
}
|
||||
}
|
||||
|
||||
func fillInMissingDates() {
|
||||
let endDate = ShowBasedOnVoteLogics.getLastDateVoteShouldExist()
|
||||
|
||||
let fetchRequest = NSFetchRequest<MoodEntry>(entityName: "MoodEntry")
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: false)]
|
||||
let entries = try! viewContext.fetch(fetchRequest)
|
||||
|
||||
if let firstEntry = entries.last?.forDate {
|
||||
let allDates: [Date] = Date.dates(from: firstEntry, to: endDate).map({
|
||||
let zeroDate = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: $0)!
|
||||
return zeroDate
|
||||
})
|
||||
|
||||
let existingEntries: [Date] = entries.compactMap({
|
||||
if let date = $0.forDate {
|
||||
let zeroDate = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: date)!
|
||||
return zeroDate
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
let allDatesSet = Set(allDates)
|
||||
let existingEntriesSet = Set(existingEntries)
|
||||
let missing = Array(allDatesSet.subtracting(existingEntriesSet)).sorted(by: >)
|
||||
for date in missing {
|
||||
add(mood: .missing, forDate: date, entryType: .listView)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Shared/Persisence/PersistenceDELETE.swift
Normal file
29
Shared/Persisence/PersistenceDELETE.swift
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// PersistenceDELETE.swift
|
||||
// Feels
|
||||
//
|
||||
// Created by Trey Tartt on 2/17/22.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
extension PersistenceController {
|
||||
func clearDB() {
|
||||
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "MoodEntry")
|
||||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
|
||||
|
||||
do {
|
||||
try viewContext.executeAndMergeChanges(using: deleteRequest)
|
||||
try viewContext.save()
|
||||
} catch let error as NSError {
|
||||
fatalError("Unresolved error \(error), \(error.userInfo)")
|
||||
}
|
||||
}
|
||||
|
||||
func delete(forDate: Date) {
|
||||
if let entry = PersistenceController.shared.getEntry(byDate: forDate) {
|
||||
viewContext.delete(entry)
|
||||
try! viewContext.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
92
Shared/Persisence/PersistenceGET.swift
Normal file
92
Shared/Persisence/PersistenceGET.swift
Normal file
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// PersistenceGET.swift
|
||||
// Feels
|
||||
//
|
||||
// Created by Trey Tartt on 2/17/22.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
extension PersistenceController {
|
||||
public func getEntry(byDate date: Date) -> MoodEntry? {
|
||||
let startDate = Calendar.current.startOfDay(for: date)
|
||||
let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate)!
|
||||
|
||||
let predicate = NSPredicate(format: "forDate >= %@ && forDate <= %@ ",
|
||||
startDate as NSDate,
|
||||
endDate as NSDate)
|
||||
|
||||
let fetchRequest = NSFetchRequest<MoodEntry>(entityName: "MoodEntry")
|
||||
fetchRequest.predicate = predicate
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: true)]
|
||||
let data = try! viewContext.fetch(fetchRequest)
|
||||
return data.first
|
||||
}
|
||||
|
||||
public func getData(startDate: Date, endDate: Date, includedDays: [Int]) -> [MoodEntry] {
|
||||
try! viewContext.setQueryGenerationFrom(.current)
|
||||
viewContext.refreshAllObjects()
|
||||
|
||||
var includedDays16 = [Int16]()
|
||||
|
||||
if includedDays.isEmpty {
|
||||
includedDays16 = [Int16(1), Int16(2), Int16(3), Int16(4), Int16(5), Int16(6), Int16(7)]
|
||||
} else {
|
||||
includedDays16 = includedDays.map({
|
||||
Int16($0)
|
||||
})
|
||||
}
|
||||
let predicate = NSPredicate(format: "forDate >= %@ && forDate <= %@ && weekDay IN %@",
|
||||
startDate as NSDate,
|
||||
endDate as NSDate,
|
||||
includedDays16)
|
||||
|
||||
let fetchRequest = NSFetchRequest<MoodEntry>(entityName: "MoodEntry")
|
||||
fetchRequest.predicate = predicate
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: true)]
|
||||
let data = try! viewContext.fetch(fetchRequest)
|
||||
return data
|
||||
}
|
||||
|
||||
public func splitIntoYearMonth() -> [Int: [Int: [MoodEntry]]] {
|
||||
let data = PersistenceController.shared.getData(startDate: Date(timeIntervalSince1970: 0),
|
||||
endDate: Date(),
|
||||
includedDays: [1,2,3,4,5,6,7]).sorted(by: {
|
||||
$0.forDate! < $1.forDate!
|
||||
})
|
||||
var returnData = [Int: [Int: [MoodEntry]]]()
|
||||
|
||||
if let earliestEntry = data.first,
|
||||
let lastEntry = data.last {
|
||||
|
||||
let calendar = Calendar.current
|
||||
let components = calendar.dateComponents([.year], from: earliestEntry.forDate!)
|
||||
let earliestYear = components.year!
|
||||
|
||||
let latestComponents = calendar.dateComponents([.year], from: lastEntry.forDate!)
|
||||
let latestYear = latestComponents.year!
|
||||
|
||||
for year in earliestYear...latestYear {
|
||||
var allMonths = [Int: [MoodEntry]]()
|
||||
|
||||
for month in (1...12) {
|
||||
var components = DateComponents()
|
||||
components.month = month
|
||||
components.year = year
|
||||
let startDateOfMonth = Calendar.current.date(from: components)!
|
||||
|
||||
let items = data.filter({ entry in
|
||||
let components = calendar.dateComponents([.month, .year], from: startDateOfMonth)
|
||||
let entryComponents = calendar.dateComponents([.month, .year], from: entry.forDate!)
|
||||
return (components.month == entryComponents.month && components.year == entryComponents.year)
|
||||
})
|
||||
if !items.isEmpty {
|
||||
allMonths[month] = items
|
||||
}
|
||||
}
|
||||
returnData[year] = allMonths
|
||||
}
|
||||
}
|
||||
return returnData
|
||||
}
|
||||
}
|
||||
85
Shared/Persisence/PersistenceHelper.swift
Normal file
85
Shared/Persisence/PersistenceHelper.swift
Normal file
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// PersistenceHelper.swift
|
||||
// Feels (iOS)
|
||||
//
|
||||
// Created by Trey Tartt on 2/17/22.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
extension PersistenceController {
|
||||
private var childContext: NSManagedObjectContext {
|
||||
return NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
|
||||
}
|
||||
|
||||
public func randomEntries(count: Int) -> [MoodEntry] {
|
||||
var entries = [MoodEntry]()
|
||||
|
||||
for idx in 0..<count {
|
||||
let newItem = MoodEntry(context: childContext)
|
||||
newItem.timestamp = Calendar.current.date(byAdding: .day, value: -idx, to: Date())
|
||||
newItem.moodValue = Int16(Mood.allValues.randomElement()!.rawValue)
|
||||
let date = Calendar.current.date(byAdding: .day, value: -idx, to: Date())!
|
||||
newItem.forDate = date
|
||||
newItem.weekDay = Int16(Calendar.current.component(.weekday, from: date))
|
||||
newItem.canEdit = true
|
||||
newItem.canDelete = true
|
||||
entries.append(newItem)
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
func populateMemory() {
|
||||
for idx in 1..<25 {
|
||||
let newItem = MoodEntry(context: viewContext)
|
||||
newItem.timestamp = Calendar.current.date(byAdding: .day, value: -idx, to: Date())
|
||||
newItem.moodValue = Int16(Mood.allValues.randomElement()!.rawValue)
|
||||
newItem.canEdit = true
|
||||
newItem.canDelete = true
|
||||
|
||||
let date = Calendar.current.date(byAdding: .day, value: -idx, to: Date())!
|
||||
newItem.forDate = date
|
||||
newItem.weekDay = Int16(Calendar.current.component(.weekday, from: date))
|
||||
}
|
||||
do {
|
||||
try viewContext.save()
|
||||
} catch {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
let nsError = error as NSError
|
||||
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
|
||||
}
|
||||
}
|
||||
|
||||
func populateTestData() {
|
||||
for idx in 1..<120 {
|
||||
let newItem = MoodEntry(context: viewContext)
|
||||
newItem.timestamp = Date()
|
||||
newItem.moodValue = Int16(Mood.allValues.randomElement()!.rawValue)
|
||||
newItem.canEdit = true
|
||||
newItem.canDelete = true
|
||||
|
||||
let date = Calendar.current.date(byAdding: .day, value: -idx, to: Date())!
|
||||
newItem.forDate = date
|
||||
newItem.weekDay = Int16(Calendar.current.component(.weekday, from: date))
|
||||
}
|
||||
do {
|
||||
try viewContext.save()
|
||||
} catch {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
let nsError = error as NSError
|
||||
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
|
||||
}
|
||||
}
|
||||
|
||||
func longestStreak() -> [MoodEntry] {
|
||||
// let predicate = NSPredicate(format: "forDate == %@", date as NSDate)
|
||||
|
||||
let fetchRequest = NSFetchRequest<MoodEntry>(entityName: "MoodEntry")
|
||||
// fetchRequest.predicate = predicate
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: true)]
|
||||
let data = try! viewContext.fetch(fetchRequest)
|
||||
return data
|
||||
}
|
||||
}
|
||||
@@ -1,340 +0,0 @@
|
||||
//
|
||||
// Persistence.swift
|
||||
// Shared
|
||||
//
|
||||
// Created by Trey Tartt on 1/5/22.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import SwiftUI
|
||||
|
||||
class PersistenceController {
|
||||
@AppStorage(UserDefaultsStore.Keys.useCloudKit.rawValue, store: GroupUserDefaults.groupDefaults) private var useCloudKit = false
|
||||
|
||||
static let shared = PersistenceController.persistenceController
|
||||
|
||||
private static var persistenceController: PersistenceController {
|
||||
return PersistenceController(inMemory: false)
|
||||
}
|
||||
|
||||
public var viewContext: NSManagedObjectContext {
|
||||
return PersistenceController.shared.container.viewContext
|
||||
}
|
||||
|
||||
private var childContext: NSManagedObjectContext {
|
||||
return NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
|
||||
}
|
||||
|
||||
|
||||
public var switchContainerListeners = [(() -> Void)]()
|
||||
|
||||
public func getEntry(byDate date: Date) -> MoodEntry? {
|
||||
let predicate = NSPredicate(format: "forDate == %@",
|
||||
date as NSDate)
|
||||
|
||||
let fetchRequest = NSFetchRequest<MoodEntry>(entityName: "MoodEntry")
|
||||
fetchRequest.predicate = predicate
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: true)]
|
||||
let data = try! viewContext.fetch(fetchRequest)
|
||||
return data.first
|
||||
}
|
||||
|
||||
public func add(mood: Mood, forDate date: Date, entryType: EntryType) {
|
||||
if let existingEntry = getEntry(byDate: date) {
|
||||
viewContext.delete(existingEntry)
|
||||
try? viewContext.save()
|
||||
}
|
||||
|
||||
let newItem = MoodEntry(context: viewContext)
|
||||
newItem.timestamp = Date()
|
||||
newItem.moodValue = Int16(mood.rawValue)
|
||||
newItem.forDate = date
|
||||
newItem.weekDay = Int16(Calendar.current.component(.weekday, from: date))
|
||||
newItem.canEdit = true
|
||||
newItem.canDelete = true
|
||||
newItem.entryType = Int16(entryType.rawValue)
|
||||
|
||||
do {
|
||||
try viewContext.save()
|
||||
} catch {
|
||||
let nsError = error as NSError
|
||||
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
|
||||
}
|
||||
}
|
||||
|
||||
public var earliestEntry: MoodEntry? {
|
||||
let fetchRequest = NSFetchRequest<MoodEntry>(entityName: "MoodEntry")
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: true)]
|
||||
let first = try! viewContext.fetch(fetchRequest).first
|
||||
return first ?? nil
|
||||
}
|
||||
|
||||
public var latestEntry: MoodEntry? {
|
||||
let fetchRequest = NSFetchRequest<MoodEntry>(entityName: "MoodEntry")
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: true)]
|
||||
let last = try! viewContext.fetch(fetchRequest).last
|
||||
return last ?? nil
|
||||
}
|
||||
|
||||
public func getData(startDate: Date, endDate: Date, includedDays: [Int]) -> [MoodEntry] {
|
||||
try! viewContext.setQueryGenerationFrom(.current)
|
||||
viewContext.refreshAllObjects()
|
||||
|
||||
var includedDays16 = [Int16]()
|
||||
|
||||
if includedDays.isEmpty {
|
||||
includedDays16 = [Int16(1), Int16(2), Int16(3), Int16(4), Int16(5), Int16(6), Int16(7)]
|
||||
} else {
|
||||
includedDays16 = includedDays.map({
|
||||
Int16($0)
|
||||
})
|
||||
}
|
||||
let predicate = NSPredicate(format: "forDate >= %@ && forDate <= %@ && weekDay IN %@",
|
||||
startDate as NSDate,
|
||||
endDate as NSDate,
|
||||
includedDays16)
|
||||
|
||||
let fetchRequest = NSFetchRequest<MoodEntry>(entityName: "MoodEntry")
|
||||
fetchRequest.predicate = predicate
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: true)]
|
||||
let data = try! viewContext.fetch(fetchRequest)
|
||||
return data
|
||||
}
|
||||
|
||||
func populateTestData() {
|
||||
for idx in 1..<120 {
|
||||
let newItem = MoodEntry(context: viewContext)
|
||||
newItem.timestamp = Date()
|
||||
newItem.moodValue = Int16(Mood.allValues.randomElement()!.rawValue)
|
||||
newItem.canEdit = true
|
||||
newItem.canDelete = true
|
||||
|
||||
let date = Calendar.current.date(byAdding: .day, value: -idx, to: Date())!
|
||||
newItem.forDate = date
|
||||
newItem.weekDay = Int16(Calendar.current.component(.weekday, from: date))
|
||||
}
|
||||
do {
|
||||
try viewContext.save()
|
||||
} catch {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
let nsError = error as NSError
|
||||
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
|
||||
}
|
||||
}
|
||||
|
||||
func populateMemory() {
|
||||
for idx in 1..<25 {
|
||||
let newItem = MoodEntry(context: viewContext)
|
||||
newItem.timestamp = Calendar.current.date(byAdding: .day, value: -idx, to: Date())
|
||||
newItem.moodValue = Int16(Mood.allValues.randomElement()!.rawValue)
|
||||
newItem.canEdit = true
|
||||
newItem.canDelete = true
|
||||
|
||||
let date = Calendar.current.date(byAdding: .day, value: -idx, to: Date())!
|
||||
newItem.forDate = date
|
||||
newItem.weekDay = Int16(Calendar.current.component(.weekday, from: date))
|
||||
}
|
||||
do {
|
||||
try viewContext.save()
|
||||
} catch {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
let nsError = error as NSError
|
||||
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
|
||||
}
|
||||
}
|
||||
|
||||
func fillInMissingDates() {
|
||||
let latestVoteUnLocked = UserDefaultsStore.getOnboarding().ableToVoteBasedOnCurentTime()
|
||||
let inputDay = UserDefaultsStore.getOnboarding().inputDay
|
||||
|
||||
var endDate: Date?
|
||||
|
||||
switch (latestVoteUnLocked, inputDay) {
|
||||
case (true, .Previous):
|
||||
endDate = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
|
||||
case (true, .Today):
|
||||
endDate = Date()
|
||||
case (false, .Previous):
|
||||
endDate = Calendar.current.date(byAdding: .day, value: -2, to: Date())!
|
||||
case (false, .Today):
|
||||
endDate = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
|
||||
}
|
||||
|
||||
let fetchRequest = NSFetchRequest<MoodEntry>(entityName: "MoodEntry")
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: false)]
|
||||
let entries = try! viewContext.fetch(fetchRequest)
|
||||
|
||||
if let firstEntry = entries.last?.forDate {
|
||||
let allDates: [Date] = Date.dates(from: firstEntry, to: endDate!).map({
|
||||
let zeroDate = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: $0)!
|
||||
return zeroDate
|
||||
})
|
||||
|
||||
let existingEntries: [Date] = entries.compactMap({
|
||||
if let date = $0.forDate {
|
||||
let zeroDate = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: date)!
|
||||
return zeroDate
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
let allDatesSet = Set(allDates)
|
||||
let existingEntriesSet = Set(existingEntries)
|
||||
let missing = Array(allDatesSet.subtracting(existingEntriesSet)).sorted(by: >)
|
||||
for date in missing {
|
||||
add(mood: .missing, forDate: date, entryType: .listView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func clearDB() {
|
||||
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "MoodEntry")
|
||||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
|
||||
|
||||
do {
|
||||
try viewContext.executeAndMergeChanges(using: deleteRequest)
|
||||
try viewContext.save()
|
||||
} catch let error as NSError {
|
||||
fatalError("Unresolved error \(error), \(error.userInfo)")
|
||||
}
|
||||
}
|
||||
|
||||
public func randomEntries(count: Int) -> [MoodEntry] {
|
||||
var entries = [MoodEntry]()
|
||||
|
||||
for idx in 0..<count {
|
||||
let newItem = MoodEntry(context: childContext)
|
||||
newItem.timestamp = Calendar.current.date(byAdding: .day, value: -idx, to: Date())
|
||||
newItem.moodValue = Int16(Mood.allValues.randomElement()!.rawValue)
|
||||
let date = Calendar.current.date(byAdding: .day, value: -idx, to: Date())!
|
||||
newItem.forDate = date
|
||||
newItem.weekDay = Int16(Calendar.current.component(.weekday, from: date))
|
||||
newItem.canEdit = true
|
||||
newItem.canDelete = true
|
||||
entries.append(newItem)
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
lazy var container: NSPersistentContainer = {
|
||||
setupContainer()
|
||||
}()
|
||||
|
||||
func switchContainer() {
|
||||
try? viewContext.save()
|
||||
container = setupContainer()
|
||||
for item in switchContainerListeners {
|
||||
item()
|
||||
}
|
||||
}
|
||||
|
||||
private func setupContainer() -> NSPersistentContainer {
|
||||
if useCloudKit {
|
||||
container = NSPersistentCloudKitContainer(name: "Feels")
|
||||
} else {
|
||||
container = NSCustomPersistentContainer(name: "Feels")
|
||||
}
|
||||
|
||||
for description in container.persistentStoreDescriptions {
|
||||
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
|
||||
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
|
||||
description.setOption(true as NSNumber, forKey: NSMigratePersistentStoresAutomaticallyOption)
|
||||
description.setOption(true as NSNumber, forKey: NSInferMappingModelAutomaticallyOption)
|
||||
}
|
||||
|
||||
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
|
||||
self.container.viewContext.automaticallyMergesChangesFromParent = true
|
||||
|
||||
if let error = error as NSError? {
|
||||
fatalError("Unresolved error \(error), \(error.userInfo)")
|
||||
}
|
||||
})
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
init(inMemory: Bool = false) {
|
||||
container = setupContainer()
|
||||
}
|
||||
|
||||
public func splitIntoYearMonth() -> [Int: [Int: [MoodEntry]]] {
|
||||
let data = PersistenceController.shared.getData(startDate: Date(timeIntervalSince1970: 0),
|
||||
endDate: Date(),
|
||||
includedDays: [1,2,3,4,5,6,7]).sorted(by: {
|
||||
$0.forDate! < $1.forDate!
|
||||
})
|
||||
var returnData = [Int: [Int: [MoodEntry]]]()
|
||||
|
||||
if let earliestEntry = data.first,
|
||||
let lastEntry = data.last {
|
||||
|
||||
let calendar = Calendar.current
|
||||
let components = calendar.dateComponents([.year], from: earliestEntry.forDate!)
|
||||
let earliestYear = components.year!
|
||||
|
||||
let latestComponents = calendar.dateComponents([.year], from: lastEntry.forDate!)
|
||||
let latestYear = latestComponents.year!
|
||||
|
||||
for year in earliestYear...latestYear {
|
||||
var allMonths = [Int: [MoodEntry]]()
|
||||
|
||||
for month in (1...12) {
|
||||
var components = DateComponents()
|
||||
components.month = month
|
||||
components.year = year
|
||||
let startDateOfMonth = Calendar.current.date(from: components)!
|
||||
|
||||
let items = data.filter({ entry in
|
||||
let components = calendar.dateComponents([.month, .year], from: startDateOfMonth)
|
||||
let entryComponents = calendar.dateComponents([.month, .year], from: entry.forDate!)
|
||||
return (components.month == entryComponents.month && components.year == entryComponents.year)
|
||||
})
|
||||
if !items.isEmpty {
|
||||
allMonths[month] = items
|
||||
}
|
||||
}
|
||||
returnData[year] = allMonths
|
||||
}
|
||||
}
|
||||
return returnData
|
||||
}
|
||||
}
|
||||
|
||||
extension NSManagedObjectContext {
|
||||
|
||||
/// Executes the given `NSBatchDeleteRequest` and directly merges the changes to bring the given managed object context up to date.
|
||||
///
|
||||
/// - Parameter batchDeleteRequest: The `NSBatchDeleteRequest` to execute.
|
||||
/// - Throws: An error if anything went wrong executing the batch deletion.
|
||||
public func executeAndMergeChanges(using batchDeleteRequest: NSBatchDeleteRequest) throws {
|
||||
batchDeleteRequest.resultType = .resultTypeObjectIDs
|
||||
let result = try execute(batchDeleteRequest) as? NSBatchDeleteResult
|
||||
let changes: [AnyHashable: Any] = [NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? []]
|
||||
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [self])
|
||||
}
|
||||
}
|
||||
|
||||
extension PersistenceController {
|
||||
func longestStreak() -> [MoodEntry] {
|
||||
// let predicate = NSPredicate(format: "forDate == %@", date as NSDate)
|
||||
|
||||
let fetchRequest = NSFetchRequest<MoodEntry>(entityName: "MoodEntry")
|
||||
// fetchRequest.predicate = predicate
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "forDate", ascending: true)]
|
||||
let data = try! viewContext.fetch(fetchRequest)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
class NSCustomPersistentContainer: NSPersistentContainer {
|
||||
override open class func defaultDirectoryURL() -> URL {
|
||||
var storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.groupShareId)
|
||||
storeURL = storeURL?.appendingPathComponent("Feels.sqlite")
|
||||
#if DEBUG
|
||||
storeURL = storeURL?.appendingPathComponent("Feels-Debug.sqlite")
|
||||
#endif
|
||||
return storeURL!
|
||||
}
|
||||
}
|
||||
123
Shared/ShowBasedOnVoteLogics.swift
Normal file
123
Shared/ShowBasedOnVoteLogics.swift
Normal file
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// ShowBasedOnVoteLogics.swift
|
||||
// Feels (iOS)
|
||||
//
|
||||
// Created by Trey Tartt on 2/17/22.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import SwiftUI
|
||||
|
||||
|
||||
//if this is being shown we're missing an entry
|
||||
// voting time is noon
|
||||
// vote for current day
|
||||
// today at 11 am -> How as yesterday
|
||||
// today at 1 pm -> How is today
|
||||
// vote for previous day
|
||||
// today at 11 am -> How as 2 days ago
|
||||
// today at 1 pm -> How was yesterday
|
||||
class ShowBasedOnVoteLogics {
|
||||
private static var currentVoting: (passTimeToVote: Bool, dayOptions: DayOptions) {
|
||||
let passedTimeToVote = UserDefaultsStore.getOnboarding().ableToVoteBasedOnCurentTime()
|
||||
let inputDay = UserDefaultsStore.getOnboarding().inputDay
|
||||
|
||||
return (passedTimeToVote, inputDay)
|
||||
}
|
||||
|
||||
static func isMissingCurrentVote() -> Bool {
|
||||
var startDate: Date?
|
||||
|
||||
switch (currentVoting.passTimeToVote, currentVoting.dayOptions) {
|
||||
case (true, .Previous):
|
||||
// if we're passed time to vote and the voting type is previous - last vote should be -1
|
||||
startDate = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
|
||||
case (true, .Today):
|
||||
// if we're passed time to vote and the voting type is today - last vote should be current date
|
||||
startDate = Date()
|
||||
case (false, .Previous):
|
||||
// if we're NOT passed time to vote and the voting type is previous - last vote should be 2 days ago
|
||||
startDate = Calendar.current.date(byAdding: .day, value: -2, to: Date())!
|
||||
case (false, .Today):
|
||||
// if we're NOT passed time to vote and the voting type is previous - last vote should be -1
|
||||
startDate = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
|
||||
}
|
||||
|
||||
startDate = Calendar.current.startOfDay(for: startDate!)
|
||||
let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate!)!
|
||||
|
||||
let fetchRequest = NSFetchRequest<MoodEntry>(entityName: "MoodEntry")
|
||||
let fromPredicate = NSPredicate(format: "%@ <= %K", startDate!
|
||||
as NSDate, #keyPath(MoodEntry.forDate))
|
||||
let toPredicate = NSPredicate(format: "%K < %@", #keyPath(MoodEntry.forDate), endDate as NSDate)
|
||||
let datePredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [fromPredicate, toPredicate])
|
||||
fetchRequest.predicate = datePredicate
|
||||
let entries = try! PersistenceController.shared.viewContext.count(for: fetchRequest)
|
||||
|
||||
return entries < 1
|
||||
}
|
||||
|
||||
static func getVotingTitle() -> String {
|
||||
switch (currentVoting.passTimeToVote, currentVoting.dayOptions) {
|
||||
case (true, .Previous):
|
||||
// if we're passed time to vote and the voting type is previous - last vote should be -1
|
||||
return "how was yesterday"
|
||||
case (true, .Today):
|
||||
// if we're passed time to vote and the voting type is previous - last vote should be today
|
||||
return "how is today"
|
||||
case (false, .Previous):
|
||||
// if we're passed time to vote and the voting type is previous - last vote should be -2
|
||||
let lastDayVoteShouldExist = ShowBasedOnVoteLogics.getLastDateVoteShouldExist()
|
||||
return "how was \(Random.weekdayName(fromDate: lastDayVoteShouldExist))"
|
||||
case (false, .Today):
|
||||
// if we're passed time to vote and the voting type is previous - last vote should be -1
|
||||
return "how as yesterday"
|
||||
}
|
||||
}
|
||||
|
||||
static func dateForHeaderVote() -> Date? {
|
||||
var date: Date?
|
||||
|
||||
switch (currentVoting.passTimeToVote, currentVoting.dayOptions) {
|
||||
case (true, .Previous):
|
||||
// if we're passed time to vote and the voting type is previous - last vote should be -1
|
||||
date = Calendar.current.date(byAdding: .day, value: -1, to: Date())
|
||||
case (true, .Today):
|
||||
// if we're passed time to vote and the voting type is previous - last vote should be today
|
||||
date = Date()
|
||||
case (false, .Previous):
|
||||
// if we're passed time to vote and the voting type is previous - last vote should be -2
|
||||
date = Calendar.current.date(byAdding: .day, value: -2, to: Date())
|
||||
case (false, .Today):
|
||||
// if we're passed time to vote and the voting type is previous - last vote should be -1
|
||||
date = Calendar.current.date(byAdding: .day, value: -1, to: Date())
|
||||
}
|
||||
date = Calendar.current.date(byAdding: .day, value: -2, to: Date())
|
||||
if let date = date {
|
||||
return date
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
static func getLastDateVoteShouldExist() -> Date {
|
||||
var endDate: Date?
|
||||
|
||||
switch (currentVoting.passTimeToVote, currentVoting.dayOptions) {
|
||||
case (true, .Previous):
|
||||
// if we're passed time to vote and the voting type is previous - last vote should -1
|
||||
endDate = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
|
||||
case (true, .Today):
|
||||
// if we're passed time to vote and the voting type is previous - last vote should be today
|
||||
endDate = Date()
|
||||
case (false, .Previous):
|
||||
// if we're passed time to vote and the voting type is previous - last vote should be -2
|
||||
endDate = Calendar.current.date(byAdding: .day, value: -2, to: Date())!
|
||||
case (false, .Today):
|
||||
// if we're passed time to vote and the voting type is previous - last vote should be -1
|
||||
endDate = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
|
||||
}
|
||||
|
||||
return endDate!
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ struct AddMoodHeaderView: View {
|
||||
Color(theme.currentTheme.secondaryBGColor)
|
||||
|
||||
VStack {
|
||||
Text(self.getTitle())
|
||||
Text(ShowBasedOnVoteLogics.getVotingTitle())
|
||||
.font(.title)
|
||||
.foregroundColor(Color(UIColor.label))
|
||||
.padding()
|
||||
@@ -56,48 +56,8 @@ struct AddMoodHeaderView: View {
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
}
|
||||
|
||||
private func getTitle() -> String {
|
||||
//if this is being shown we're missing an entry
|
||||
// voting time is noon
|
||||
// vote for current day
|
||||
// today at 11 am -> How as yesterday
|
||||
// today at 1 pm -> How is today
|
||||
// vote for previous day
|
||||
// today at 11 am -> How as 2 days ago
|
||||
// today at 1 pm -> How was yesterday
|
||||
|
||||
let latestVoteUnLocked = UserDefaultsStore.getOnboarding().ableToVoteBasedOnCurentTime()
|
||||
let inputDay = UserDefaultsStore.getOnboarding().inputDay
|
||||
|
||||
switch (latestVoteUnLocked, inputDay) {
|
||||
case (true, .Previous):
|
||||
return "how was yesterday"
|
||||
case (true, .Today):
|
||||
return "how is today"
|
||||
case (false, .Previous):
|
||||
return "how was two days ago"
|
||||
case (false, .Today):
|
||||
return "how as yesterday"
|
||||
}
|
||||
}
|
||||
|
||||
private func addItem(withMood mood: Mood) {
|
||||
let latestVoteUnLocked = UserDefaultsStore.getOnboarding().ableToVoteBasedOnCurentTime()
|
||||
let inputDay = UserDefaultsStore.getOnboarding().inputDay
|
||||
|
||||
var date: Date?
|
||||
|
||||
switch (latestVoteUnLocked, inputDay) {
|
||||
case (true, .Previous):
|
||||
date = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
|
||||
case (true, .Today):
|
||||
date = Date()
|
||||
case (false, .Previous):
|
||||
date = Calendar.current.date(byAdding: .day, value: -2, to: Date())!
|
||||
case (false, .Today):
|
||||
date = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
|
||||
}
|
||||
if let date = date {
|
||||
if let date = ShowBasedOnVoteLogics.dateForHeaderVote() {
|
||||
addItemHeaderClosure(mood, date)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,8 +91,7 @@ struct ContentView: View {
|
||||
}
|
||||
|
||||
if let selectedEntry = selectedEntry,
|
||||
deleteEnabled,
|
||||
selectedEntry.mood != .missing {
|
||||
deleteEnabled{
|
||||
Button(String(localized: "content_view_delete_entry"), action: {
|
||||
viewModel.update(entry: selectedEntry, toMood: Mood.missing)
|
||||
showUpdateEntryAlert = false
|
||||
@@ -140,7 +139,11 @@ struct ContentView: View {
|
||||
return ""
|
||||
}
|
||||
|
||||
let components = Calendar.current.dateComponents([.day, .month, .year], from: entry.forDate!)
|
||||
guard let forDate = entry.forDate else {
|
||||
return ""
|
||||
}
|
||||
|
||||
let components = Calendar.current.dateComponents([.day, .month, .year], from: forDate)
|
||||
// let day = components.day!
|
||||
let month = components.month!
|
||||
let year = components.year!
|
||||
@@ -178,7 +181,7 @@ struct ContentView: View {
|
||||
|
||||
private var headerView: some View {
|
||||
VStack {
|
||||
if viewModel.shouldShowVotingHeader() {
|
||||
if ShowBasedOnVoteLogics.isMissingCurrentVote() {
|
||||
AddMoodHeaderView(addItemHeaderClosure: { (mood, date) in
|
||||
withAnimation {
|
||||
viewModel.add(mood: mood, forDate: date, entryType: .header)
|
||||
|
||||
Reference in New Issue
Block a user