├── .gitignore ├── DaysSince.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcuserdata │ │ └── jordibruin.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ ├── DaysSince.xcscheme │ │ ├── WidgetExtension.xcscheme │ │ └── WidgetIntents.xcscheme └── xcuserdata │ ├── jordibruin.xcuserdatad │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ │ └── xcschememanagement.plist │ └── victoria.petrova.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── DaysSince ├── AddItemViews │ ├── AddCategorySheet.swift │ ├── AddItemSheet.swift │ ├── CategoryFormSection.swift │ └── MoreSfSymbolsView.swift ├── Analytics.swift ├── AppIcons │ └── AppIcons.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcons │ │ ├── Calendar-blue │ │ │ ├── Contents.json │ │ │ ├── calendar-blue-image.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── Icon Blue.png │ │ │ └── calendar-blue.appiconset │ │ │ │ ├── 1024x1024 1.png │ │ │ │ ├── 128x128.png │ │ │ │ ├── 16x16.png │ │ │ │ ├── 256x256 1.png │ │ │ │ ├── 256x256.png │ │ │ │ ├── 32x32 1.png │ │ │ │ ├── 32x32.png │ │ │ │ ├── 512x512 1.png │ │ │ │ ├── 512x512.png │ │ │ │ ├── 64x64 1.png │ │ │ │ ├── AppIcon40x40@2x 1.png │ │ │ │ ├── AppIcon40x40@2x.png │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon-60@2x 1.png │ │ │ │ ├── Icon-60@2x.png │ │ │ │ ├── Icon-60@3x.png │ │ │ │ ├── Icon-76@2x 1.png │ │ │ │ ├── Icon-Notification@3x.png │ │ │ │ ├── Icon-Small-40 1.png │ │ │ │ ├── Icon-Small.png │ │ │ │ ├── Icon-Small@2x.png │ │ │ │ ├── Icon-Small@3x.png │ │ │ │ ├── Icon.png │ │ │ │ ├── Icon@2x.png │ │ │ │ └── iTunesArtwork@2x.png │ │ ├── Calendar-orange │ │ │ ├── Contents.json │ │ │ ├── calendar-orange-image.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── Icon Orange.png │ │ │ └── calendar-orange.appiconset │ │ │ │ ├── 1024x1024 1.png │ │ │ │ ├── 128x128.png │ │ │ │ ├── 16x16.png │ │ │ │ ├── 256x256 1.png │ │ │ │ ├── 256x256.png │ │ │ │ ├── 32x32 1.png │ │ │ │ ├── 32x32.png │ │ │ │ ├── 512x512 1.png │ │ │ │ ├── 512x512.png │ │ │ │ ├── 64x64.png │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon-60@2x.png │ │ │ │ ├── Icon-60@3x.png │ │ │ │ ├── Icon-Notification@3x.png │ │ │ │ ├── Icon-Small-40@2x.png │ │ │ │ ├── Icon-Small.png │ │ │ │ ├── Icon-Small@2x.png │ │ │ │ ├── Icon-Small@3x.png │ │ │ │ ├── Icon.png │ │ │ │ ├── Icon@2x.png │ │ │ │ └── iTunesArtwork@2x.png │ │ ├── Calendar-pink │ │ │ ├── Contents.json │ │ │ ├── calendar-pink-image.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── Icon Pink (1).png │ │ │ └── calendar-pink.appiconset │ │ │ │ ├── 1024x1024.png │ │ │ │ ├── 128x128.png │ │ │ │ ├── 16x16.png │ │ │ │ ├── 256x256 1.png │ │ │ │ ├── 256x256.png │ │ │ │ ├── 32x32 1.png │ │ │ │ ├── 32x32.png │ │ │ │ ├── 512x512 1.png │ │ │ │ ├── 512x512.png │ │ │ │ ├── 64x64.png │ │ │ │ ├── AppIcon40x40@2x.png │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon-60@2x.png │ │ │ │ ├── Icon-60@3x.png │ │ │ │ ├── Icon-Notification@3x.png │ │ │ │ ├── Icon-Small.png │ │ │ │ ├── Icon-Small@2x.png │ │ │ │ ├── Icon-Small@3x.png │ │ │ │ ├── Icon.png │ │ │ │ ├── Icon@2x.png │ │ │ │ └── iTunesArtwork@2x.png │ │ ├── Calendar-purple │ │ │ ├── Contents.json │ │ │ ├── calendar-purple-image.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── Icon Purple.png │ │ │ └── calendar-purple.appiconset │ │ │ │ ├── 1024x1024 1.png │ │ │ │ ├── 128x128.png │ │ │ │ ├── 16x16.png │ │ │ │ ├── 256x256 1.png │ │ │ │ ├── 256x256.png │ │ │ │ ├── 32x32 1.png │ │ │ │ ├── 32x32.png │ │ │ │ ├── 512x512 1.png │ │ │ │ ├── 512x512.png │ │ │ │ ├── 64x64 1.png │ │ │ │ ├── AppIcon40x40@2x 1.png │ │ │ │ ├── AppIcon40x40@2x.png │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon-60@2x.png │ │ │ │ ├── Icon-60@3x.png │ │ │ │ ├── Icon-Notification@3x.png │ │ │ │ ├── Icon-Small-40 1.png │ │ │ │ ├── Icon-Small.png │ │ │ │ ├── Icon-Small@2x.png │ │ │ │ ├── Icon-Small@3x.png │ │ │ │ ├── Icon.png │ │ │ │ ├── Icon@2x.png │ │ │ │ └── iTunesArtwork@2x.png │ │ ├── Contents.json │ │ └── Main │ │ │ ├── AppIcon-image.imageset │ │ │ ├── Contents.json │ │ │ └── appstore.png │ │ │ ├── AppIcon.appiconset │ │ │ ├── 120.png │ │ │ ├── 152.png │ │ │ ├── 76.png │ │ │ ├── 80.png │ │ │ ├── Contents.json │ │ │ ├── appstore.png │ │ │ ├── appstore1024.png │ │ │ ├── ipadNotification20.png │ │ │ ├── ipadNotification40.png │ │ │ ├── ipadPro167.png │ │ │ ├── ipadSettings29.png │ │ │ ├── ipadSettings58.png │ │ │ ├── ipadSpotlight40.png │ │ │ ├── ipadSpotlight80.png │ │ │ ├── iphone180.png │ │ │ ├── notification40.png │ │ │ ├── notification60.png │ │ │ ├── settings58.png │ │ │ ├── settings87.png │ │ │ └── spotlight120.png │ │ │ └── Contents.json │ ├── Contents.json │ ├── animalCrossingsBrown.colorset │ │ └── Contents.json │ ├── animalCrossingsGreen.colorset │ │ └── Contents.json │ ├── backgroundColor.colorset │ │ └── Contents.json │ ├── healthColor.colorset │ │ └── Contents.json │ ├── hobbiesColor.colorset │ │ └── Contents.json │ ├── lifeColor.colorset │ │ └── Contents.json │ ├── marioBlue.colorset │ │ └── Contents.json │ ├── marioRed.colorset │ │ └── Contents.json │ ├── peachDarkPink.colorset │ │ └── Contents.json │ ├── peachLightPink.colorset │ │ └── Contents.json │ ├── workColor.colorset │ │ └── Contents.json │ ├── zeldaGreen.colorset │ │ └── Contents.json │ └── zeldaYellow.colorset │ │ └── Contents.json ├── CategoriesViews │ ├── CategoriesGridView.swift │ ├── CategoryFilteredView.swift │ └── CategoryRectangleView.swift ├── ContentView.swift ├── DaysSince.entitlements ├── DaysSince │ ├── AddNewItem │ │ ├── AddItemForm.swift │ │ └── AddItemSheet.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── DaysSince_Icon-1024.png │ │ │ ├── DaysSince_Icon-20.png │ │ │ ├── DaysSince_Icon-40.png │ │ │ ├── DaysSince_Icon-40@2x.png │ │ │ ├── DaysSince_Icon-60@2x.png │ │ │ └── DaysSince_Icon-60@3x.png │ │ ├── Contents.json │ │ ├── backgroundColor.colorset │ │ │ └── Contents.json │ │ ├── healthCategoryIcon.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-health-48.png │ │ ├── healthColor.colorset │ │ │ └── Contents.json │ │ ├── hobbiesCategoryIcon.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-soccer-ball-50.png │ │ ├── hobbiesColor.colorset │ │ │ └── Contents.json │ │ ├── lifeCategoryIcon.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-trust-50 (1).png │ │ ├── lifeColor.colorset │ │ │ └── Contents.json │ │ ├── workCategoryIcon.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-work-64.png │ │ └── workColor.colorset │ │ │ └── Contents.json │ ├── Calendar │ │ ├── CalendarView.swift │ │ └── RootView.swift │ ├── Category Blocks │ │ └── CategoryFilteredView.swift │ ├── ContentView.swift │ ├── DaysSince.entitlements │ ├── DaysSinceApp.swift │ ├── EditExistingItem │ │ ├── EditTappedItemForm.swift │ │ └── EditTappedItemSheet.swift │ ├── Extensions │ │ ├── Array+Extensions.swift │ │ ├── Binding+Extensions.swift │ │ ├── Calendar+Extensions.swift │ │ └── Color+Extensions.swift │ ├── Info.plist │ ├── MainScreen │ │ ├── MainScreen.swift │ │ └── Views │ │ │ ├── BottomSection │ │ │ ├── BottomSection.swift │ │ │ ├── DSItemView.swift │ │ │ └── ItemLists │ │ │ │ └── DSItemListView.swift │ │ │ ├── MainBackgroundView.swift │ │ │ ├── SortingMenuView.swift │ │ │ └── TopSection │ │ │ ├── EditCategorySheet.swift │ │ │ ├── MenuBlockView.swift │ │ │ └── TopSection.swift │ ├── Model │ │ ├── CategoryDSItem.swift │ │ ├── DSItemReminders.swift │ │ ├── DaysSinceItem.swift │ │ └── Defaults.swift │ └── Preview Content │ │ └── Preview Assets.xcassets │ │ └── Contents.json ├── DaysSinceApp.swift ├── EditItemViews │ └── EditTappedItemSheet.swift ├── Extensions │ ├── Array+Extensions.swift │ ├── Bundle+Extensions.swift │ ├── Calendar+Extensions.swift │ ├── Color+Extensions.swift │ ├── Date+Extensions.swift │ ├── Defaults+Extension+Colors.swift │ └── UIApplication+Extensions.swift ├── Info.plist ├── Localizable.strings ├── Managers │ ├── CategoryManager.swift │ ├── NotificationManager.swift │ └── ReviewManager.swift ├── Model │ ├── AlternativeIcon.swift │ ├── Category.swift │ ├── CategoryColor.swift │ ├── DSItem.swift │ └── Defaults.swift ├── Onboarding │ ├── OnboardingScreen.swift │ └── Pages │ │ ├── AnimateText.swift │ │ ├── CreateFirstEvent.swift │ │ └── Introduction.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Settings │ ├── ColorThemeView.swift │ ├── DetailedTimeDisplayMode.swift │ ├── LinkButton.swift │ ├── MainAppColorPicker.swift │ ├── SettingsReviewButton.swift │ ├── SettingsScreen.swift │ ├── ShareButton.swift │ ├── SupportButton.swift │ ├── ThemeButton.swift │ ├── ThemeView.swift │ └── WishKitView.swift └── Supporter │ ├── Analytics │ └── AnalyticsOld.swift │ ├── CHANGELOG.md │ ├── Deeplink.swift │ ├── Design │ └── Design.swift │ ├── Manager │ └── SupportFetcher.swift │ ├── Model │ ├── SupportColor.swift │ ├── SupportItemPage.swift │ ├── SupportItemable.swift │ ├── SupportLocale.swift │ └── SupportTypes │ │ ├── ChangelogItem.swift │ │ ├── FAQItem.swift │ │ ├── HeaderItem.swift │ │ ├── HighlightedItem.swift │ │ ├── HighlightedSection.swift │ │ ├── SupportContactItem.swift │ │ ├── SupportItems.swift │ │ ├── SupportReleaseNote.swift │ │ └── SupportSection.swift │ ├── README.md │ ├── Sample JSON │ ├── FAQOnly.json │ ├── HighlightsOnly.json │ └── fullSample.json │ ├── Screens │ ├── SupportItemDetailView.swift │ └── SupportScreen.swift │ ├── Utilities │ ├── AsyncImage │ │ └── BackPortedAsyncImage.swift │ ├── HooliganBundle+Extensions.swift │ ├── HooliganColor+Extensions.swift │ └── HooliganString+Extensions.swift │ ├── Video │ └── HooliganVideoPlayerController.swift │ └── Views │ ├── Contact │ ├── ContactItemView.swift │ └── SupportContactSection.swift │ ├── Detail │ └── SupportItemPageView.swift │ ├── FAQ │ ├── FAQDetailView.swift │ ├── FAQItemView.swift │ └── SupportFAQSection.swift │ ├── Header │ ├── SupportHeaderSection.swift │ └── SupportHeaderView.swift │ ├── Highlighted │ ├── HighlightedItemView.swift │ └── SupportHighlightedSection.swift │ └── ReleaseNotes │ ├── ChangelogUpdateCell.swift │ ├── ChangelogView.swift │ ├── NewUpdateBanner.swift │ └── ReleaseNoteDetail.swift ├── README.md ├── Widget ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── WidgetBackground.colorset │ │ └── Contents.json ├── Base.lproj │ └── Widget.intentdefinition ├── Families Single Event Widget │ ├── SingleEventWidget_Circular.swift │ ├── SingleEventWidget_Inline.swift │ ├── SingleEventWidget_Rectangular.swift │ └── SingleEventWidget_Standard.swift ├── Info.plist ├── MultipleEventsWidgetView.swift ├── Provider │ ├── MultiEventProvider.swift │ └── SingleEventProvider.swift ├── SingleEventWidgetView.swift ├── Widget.swift ├── WidgetContent.swift └── en.lproj │ └── Widget.strings ├── WidgetExtension.entitlements └── WidgetIntents ├── Info.plist ├── IntentHandler.swift └── WidgetIntents.entitlements /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | # macOS 9 | .DS_Store 10 | 11 | # Cursor Editor 12 | .cursor/ 13 | -------------------------------------------------------------------------------- /DaysSince.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DaysSince.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DaysSince.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "6345dfc224494e3898a039d452efd7088b7bf3b1304b454c78790dd8c2c14abd", 3 | "pins" : [ 4 | { 5 | "identity" : "defaults", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/sindresorhus/Defaults", 8 | "state" : { 9 | "branch" : "main", 10 | "revision" : "38925e3cfacf3fb89a81a35b1cd44fd5a5b7e0fa" 11 | } 12 | }, 13 | { 14 | "identity" : "swiftsdk", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/TelemetryDeck/SwiftSDK", 17 | "state" : { 18 | "revision" : "5cb2982470dc4830c4e7de5da4a5af1c50c304b4", 19 | "version" : "2.9.2" 20 | } 21 | }, 22 | { 23 | "identity" : "wishkit-ios", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/wishkit/wishkit-ios.git", 26 | "state" : { 27 | "branch" : "main", 28 | "revision" : "dad332bcab0bba1ae61c914f5fe84a78510ae759" 29 | } 30 | }, 31 | { 32 | "identity" : "wishkit-ios-shared", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/wishkit/wishkit-ios-shared.git", 35 | "state" : { 36 | "revision" : "118c9c482e4ad57c65d664283516425b98616483", 37 | "version" : "1.4.3" 38 | } 39 | } 40 | ], 41 | "version" : 3 42 | } 43 | -------------------------------------------------------------------------------- /DaysSince.xcodeproj/project.xcworkspace/xcuserdata/jordibruin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince.xcodeproj/project.xcworkspace/xcuserdata/jordibruin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /DaysSince.xcodeproj/xcuserdata/jordibruin.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /DaysSince.xcodeproj/xcuserdata/jordibruin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DaysSince.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | WidgetExtension.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | WidgetIntents.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 1 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /DaysSince.xcodeproj/xcuserdata/victoria.petrova.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DaysSince.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | WidgetExtension.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 2 16 | 17 | WidgetIntents.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 1 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /DaysSince/AddItemViews/CategoryFormSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CategoryFormSection.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 11/25/23. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | 11 | struct CategoryFormSection: View { 12 | @Environment(\.colorScheme) var colorScheme 13 | @Default(.categories) var categories 14 | 15 | @Binding var selectedCategory: Category? 16 | @Binding var showCategorySheet: Bool 17 | 18 | var accentColor: Color { selectedCategory == nil ? Color.primary : selectedCategory?.color.color == .black && colorScheme == .dark ? Color.white : selectedCategory!.color.color } 19 | 20 | 21 | var body: some View { 22 | Section { 23 | categoriesList 24 | addCategoryButton 25 | } header: { 26 | Text("Category") 27 | } 28 | } 29 | 30 | var categoriesList: some View { 31 | ForEach(categories, id: \.id) { category in 32 | Button { 33 | withAnimation { selectedCategory = category } 34 | } label: { 35 | HStack { 36 | Image(systemName: category.emoji) 37 | .foregroundColor(selectedCategory == category ? accentColor : .primary) 38 | .frame(width: 40) 39 | 40 | Text(category.name) 41 | Spacer() 42 | 43 | if selectedCategory == category { 44 | Image(systemName: "checkmark.circle.fill") 45 | .imageScale(.large) 46 | .foregroundColor(accentColor) 47 | } 48 | } 49 | } 50 | .foregroundColor(.primary) 51 | } 52 | } 53 | 54 | var addCategoryButton: some View { 55 | HStack { 56 | Spacer() 57 | Button { 58 | showCategorySheet = true 59 | } label: { 60 | HStack { 61 | Image(systemName: "plus.rectangle.fill.on.rectangle.fill") 62 | .foregroundColor(.primary) 63 | Text("Add Category") 64 | .foregroundColor(.primary) 65 | .bold() 66 | } 67 | } 68 | .padding() 69 | .background(Color.primary.opacity(0.1)) 70 | .cornerRadius(16) 71 | } 72 | .buttonStyle(BorderlessButtonStyle()) 73 | } 74 | } 75 | 76 | struct CategoryFormSection_Previews: PreviewProvider { 77 | static var previews: some View { 78 | CategoryFormSection(selectedCategory: .constant(nil), showCategorySheet: .constant(false)) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /DaysSince/Analytics.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Analytics.swift 3 | // DaysSince 4 | // 5 | // Created by Victoria Petrova on 06/04/2025. 6 | // 7 | 8 | import Foundation 9 | import TelemetryDeck 10 | 11 | struct Analytics { 12 | 13 | static func send(_ option: AnalyticType, with additionalParameters: [String: String]? = nil) { 14 | 15 | if let additionalParameters { 16 | TelemetryDeck.signal(option.rawValue, parameters: additionalParameters) 17 | } else { 18 | TelemetryDeck.signal(option.rawValue) 19 | } 20 | // debugPrint("📊 \(option.rawValue) \(additionalParameters?["recipeID"] ?? "")") 21 | // debugPrint(📊 \(option.rawValue) \(additionalParameters)) 22 | } 23 | 24 | } 25 | 26 | enum AnalyticType: String, Hashable { 27 | 28 | case launchApp 29 | case addNewEvent 30 | case editEvent 31 | 32 | case updateCategory 33 | case addNewCategory 34 | 35 | case chooseIcon 36 | case chooseTheme 37 | 38 | case settingsReview 39 | case reviewPrompt 40 | case detailedModeOn 41 | 42 | // case proTheme 43 | // case proIcons 44 | // case proSettings 45 | // case proHome 46 | // case proOnboarding 47 | // case proStartPurchase 48 | // case proPurchasedInOnboarding 49 | // case proPurchased 50 | 51 | func stringValue() -> String { 52 | switch self { 53 | case .launchApp: 54 | return "launchApp" 55 | case .editEvent: 56 | return "editEvent" 57 | case .updateCategory: 58 | return "updateCategory" 59 | case .addNewCategory: 60 | return "addNewCategory" 61 | case .chooseIcon: 62 | return "chooseIcon" 63 | case .addNewEvent: 64 | return "addNewEvent" 65 | case .chooseTheme: 66 | return "chooseTheme" 67 | case .reviewPrompt: 68 | return "reviewPrompt" 69 | case .settingsReview: 70 | return "settingsReview" 71 | case .detailedModeOn: 72 | return "detailedModeOn" 73 | } 74 | } 75 | } 76 | 77 | public func isSimulatorOrTestFlight() -> Bool { 78 | guard let path = Bundle.main.appStoreReceiptURL?.path else { 79 | return false 80 | } 81 | return path.contains("CoreSimulator") || path.contains("sandboxReceipt") 82 | } 83 | 84 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue-image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon Blue.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue-image.imageset/Icon Blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue-image.imageset/Icon Blue.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/1024x1024 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/1024x1024 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/128x128.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/16x16.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/256x256 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/256x256 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/256x256.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/32x32 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/32x32 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/32x32.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/512x512 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/512x512 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/512x512.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/64x64 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/64x64 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/AppIcon40x40@2x 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/AppIcon40x40@2x 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/AppIcon40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/AppIcon40x40@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-60@2x 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-60@2x 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-76@2x 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-76@2x 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-Notification@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-Notification@3x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-Small-40 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-Small-40 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-blue/calendar-blue.appiconset/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange-image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon Orange.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange-image.imageset/Icon Orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange-image.imageset/Icon Orange.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/1024x1024 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/1024x1024 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/128x128.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/16x16.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/256x256 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/256x256 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/256x256.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/32x32 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/32x32 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/32x32.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/512x512 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/512x512 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/512x512.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/64x64.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon-Notification@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon-Notification@3x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon-Small-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon-Small-40@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-orange/calendar-orange.appiconset/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink-image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon Pink (1).png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink-image.imageset/Icon Pink (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink-image.imageset/Icon Pink (1).png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/1024x1024.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/128x128.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/16x16.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/256x256 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/256x256 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/256x256.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/32x32 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/32x32 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/32x32.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/512x512 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/512x512 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/512x512.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/64x64.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/AppIcon40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/AppIcon40x40@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/Icon-Notification@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/Icon-Notification@3x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/Icon.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-pink/calendar-pink.appiconset/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple-image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon Purple.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple-image.imageset/Icon Purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple-image.imageset/Icon Purple.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/1024x1024 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/1024x1024 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/128x128.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/16x16.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/256x256 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/256x256 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/256x256.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/32x32 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/32x32 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/32x32.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/512x512 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/512x512 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/512x512.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/64x64 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/64x64 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/AppIcon40x40@2x 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/AppIcon40x40@2x 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/AppIcon40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/AppIcon40x40@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon-Notification@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon-Notification@3x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon-Small-40 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon-Small-40 1.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Calendar-purple/calendar-purple.appiconset/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon-image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "appstore.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon-image.imageset/appstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon-image.imageset/appstore.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/appstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/appstore.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/appstore1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/appstore1024.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/ipadNotification20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/ipadNotification20.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/ipadNotification40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/ipadNotification40.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/ipadPro167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/ipadPro167.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/ipadSettings29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/ipadSettings29.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/ipadSettings58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/ipadSettings58.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/ipadSpotlight40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/ipadSpotlight40.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/ipadSpotlight80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/ipadSpotlight80.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/iphone180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/iphone180.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/notification40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/notification40.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/notification60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/notification60.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/settings58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/settings58.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/settings87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/settings87.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/spotlight120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/Assets.xcassets/AppIcons/Main/AppIcon.appiconset/spotlight120.png -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/AppIcons/Main/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/animalCrossingsBrown.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.392", 9 | "green" : "0.514", 10 | "red" : "0.604" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.306", 27 | "green" : "0.397", 28 | "red" : "0.465" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/animalCrossingsGreen.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.463", 9 | "green" : "0.804", 10 | "red" : "0.490" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.384", 27 | "green" : "0.667", 28 | "red" : "0.407" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/backgroundColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.925", 9 | "green" : "0.862", 10 | "red" : "0.890" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.770", 27 | "green" : "0.709", 28 | "red" : "0.732" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/healthColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.357", 9 | "green" : "0.678", 10 | "red" : "0.951" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.285", 27 | "green" : "0.536", 28 | "red" : "0.745" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/hobbiesColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.906", 9 | "green" : "0.774", 10 | "red" : "0.432" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.651", 27 | "green" : "0.552", 28 | "red" : "0.304" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/lifeColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.953", 9 | "green" : "0.475", 10 | "red" : "0.549" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.697", 27 | "green" : "0.344", 28 | "red" : "0.394" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/marioBlue.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.863", 9 | "green" : "0.412", 10 | "red" : "0.177" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.684", 27 | "green" : "0.323", 28 | "red" : "0.136" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/marioRed.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.286", 9 | "green" : "0.303", 10 | "red" : "0.884" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.206", 27 | "green" : "0.218", 28 | "red" : "0.618" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/peachDarkPink.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.545", 9 | "green" : "0.309", 10 | "red" : "0.853" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.438", 27 | "green" : "0.247", 28 | "red" : "0.679" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/peachLightPink.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.835", 9 | "green" : "0.680", 10 | "red" : "0.909" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.678", 27 | "green" : "0.542", 28 | "red" : "0.730" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/workColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x89", 9 | "green" : "0x63", 10 | "red" : "0xE4" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.415", 27 | "green" : "0.299", 28 | "red" : "0.676" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/zeldaGreen.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.431", 9 | "green" : "0.643", 10 | "red" : "0.604" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.247", 27 | "green" : "0.380", 28 | "red" : "0.345" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/Assets.xcassets/zeldaYellow.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.169", 9 | "green" : "0.821", 10 | "red" : "0.959" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.158", 27 | "green" : "0.781", 28 | "red" : "0.915" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/CategoriesViews/CategoriesGridView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CategoriesGridView.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 5/23/22. 6 | // 7 | 8 | import SwiftUI 9 | // 10 | // struct CategoriesGridView: View { 11 | // @Binding var selectedCategory: CategoryDSItem? 12 | // 13 | // @State var addItem: Bool 14 | // 15 | // var body: some View { 16 | // HStack(alignment: .top, spacing: 0) { 17 | // VStack { 18 | // CategoryRectangleView(category: .work, selectedCategory: selectedCategory) 19 | // .onTapGesture { 20 | // selectedCategory = .work 21 | // } 22 | // CategoryRectangleView(category: .life, selectedCategory: selectedCategory) 23 | // .onTapGesture { 24 | // selectedCategory = .life 25 | // } 26 | // } 27 | // VStack { 28 | // CategoryRectangleView(category: .health, selectedCategory: selectedCategory) 29 | // .onTapGesture { 30 | // selectedCategory = .health 31 | // } 32 | // CategoryRectangleView(category: .hobbies, selectedCategory: selectedCategory) 33 | // .onTapGesture { 34 | // selectedCategory = .hobbies 35 | // } 36 | // } 37 | // } 38 | // } 39 | // } 40 | // 41 | // struct CategoriesGridView_Previews: PreviewProvider { 42 | // static var previews: some View { 43 | // CategoriesGridView(selectedCategory: .constant(CategoryDSItem.hobbies), addItem: true) 44 | // } 45 | // } 46 | -------------------------------------------------------------------------------- /DaysSince/CategoriesViews/CategoryRectangleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CategoryRectangleView.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 4/11/22. 6 | // 7 | 8 | import SwiftUI 9 | // 10 | // struct CategoryRectangleView: View { 11 | //// init(category: CategoryDaysSinceItem = CategoryDaysSinceItem.health, selectedCategory: CategoryDaysSinceItem? = CategoryDaysSinceItem.hobbies) { 12 | //// self.category = category 13 | //// self.selectedCategory = selectedCategory 14 | //// } 15 | // 16 | // var category: CategoryDSItem 17 | // 18 | // var selectedCategory: CategoryDSItem? 19 | // 20 | // var body: some View { 21 | // ZStack(alignment: .leading) { 22 | // backgroundColor 23 | // categoryBlockContent 24 | // } 25 | // .clipShape(RoundedRectangle(cornerRadius: 24)) 26 | // .padding(.horizontal, 12) 27 | // .padding(.vertical, 4) 28 | // .shadow(color: selectedCategory == nil ? category.color : selectedCategory == category ? category.color : category.color.opacity(0.2), radius: 10, x: 0, y: 5) 29 | // } 30 | // 31 | // var backgroundColor: some View { 32 | // category.color 33 | // .opacity(selectedCategory == nil ? 1 : selectedCategory == category ? 1 : 0.7) 34 | // } 35 | // 36 | // var categoryBlockContent: some View { 37 | // VStack(alignment: .center) { 38 | // emoji 39 | // titleText 40 | // } 41 | // .foregroundColor(.white) 42 | // .padding() 43 | // } 44 | // 45 | // @ViewBuilder 46 | // var emoji: some View { 47 | // Image(category.emoji) 48 | // } 49 | // 50 | // var titleText: some View { 51 | // Text(category.name) 52 | // .font(.system(.title2, design: .rounded)) 53 | // .bold() 54 | // } 55 | // } 56 | // 57 | // struct CategoryRectangleView_Previews: PreviewProvider { 58 | // static var previews: some View { 59 | // CategoryRectangleView(category: .health, selectedCategory: nil) 60 | // } 61 | // } 62 | -------------------------------------------------------------------------------- /DaysSince/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 3/28/22. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | import WishKit 11 | 12 | struct ContentView: View { 13 | @AppStorage("hasSeenOnboarding") var hasSeenOnboarding: Bool = false 14 | 15 | @AppStorage("items", store: UserDefaults(suiteName: "group.goodsnooze.dayssince")) var items: [DSItem] = [] 16 | 17 | @AppStorage("items", store: UserDefaults(suiteName: "group.goodsnooze.dayssince")) var oldItems: [oldDSItem] = [] 18 | 19 | @AppStorage("isDaysDisplayModeDetailed", store: UserDefaults(suiteName: "group.goodsnooze.dayssince")) var isDaysDisplayModeDetailed: Bool = true 20 | 21 | @AppStorage("migratedFromOld") var migratedFromOld: Bool = false 22 | 23 | @Default(.categories) var categories 24 | @Default(.mainColor) var mainColor 25 | @Default(.backgroundColor) var backgroundColor 26 | 27 | init() { 28 | WishKit.configure(with: "6443C4AA-4663-4A27-89E5-846598908A4E") 29 | WishKit.config.statusBadge = .show 30 | } 31 | 32 | var body: some View { 33 | if hasSeenOnboarding { 34 | MainScreen(items: $items, 35 | isDaysDisplayModeDetailed: $isDaysDisplayModeDetailed) 36 | .onAppear { 37 | WishKit.theme.primaryColor = mainColor 38 | 39 | if !migratedFromOld { 40 | if !oldItems.isEmpty { 41 | let newItems = oldItems.map { oldItem in 42 | DSItem( 43 | id: oldItem.id, 44 | name: oldItem.name, 45 | category: Category.placeholderCategory(), 46 | dateLastDone: oldItem.dateLastDone, 47 | remindersEnabled: oldItem.remindersEnabled, 48 | reminder: oldItem.reminder, 49 | reminderNotificationID: oldItem.reminderNotificationID 50 | ) 51 | } 52 | items = items + newItems 53 | migratedFromOld = true 54 | } 55 | } 56 | } 57 | } else { 58 | OnboardingScreen(hasSeenOnboarding: $hasSeenOnboarding, items: $items) 59 | } 60 | } 61 | } 62 | 63 | struct ContentView_Previews: PreviewProvider { 64 | static var previews: some View { 65 | Group { 66 | ContentView() 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /DaysSince/DaysSince.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.goodsnooze.dayssince 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "filename" : "DaysSince_Icon-40@2x.png", 25 | "idiom" : "iphone", 26 | "scale" : "2x", 27 | "size" : "40x40" 28 | }, 29 | { 30 | "idiom" : "iphone", 31 | "scale" : "3x", 32 | "size" : "40x40" 33 | }, 34 | { 35 | "filename" : "DaysSince_Icon-60@2x.png", 36 | "idiom" : "iphone", 37 | "scale" : "2x", 38 | "size" : "60x60" 39 | }, 40 | { 41 | "filename" : "DaysSince_Icon-60@3x.png", 42 | "idiom" : "iphone", 43 | "scale" : "3x", 44 | "size" : "60x60" 45 | }, 46 | { 47 | "filename" : "DaysSince_Icon-20.png", 48 | "idiom" : "ipad", 49 | "scale" : "1x", 50 | "size" : "20x20" 51 | }, 52 | { 53 | "filename" : "DaysSince_Icon-40.png", 54 | "idiom" : "ipad", 55 | "scale" : "2x", 56 | "size" : "20x20" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "1x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "2x", 66 | "size" : "29x29" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "1x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "2x", 76 | "size" : "40x40" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "1x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "76x76" 87 | }, 88 | { 89 | "idiom" : "ipad", 90 | "scale" : "2x", 91 | "size" : "83.5x83.5" 92 | }, 93 | { 94 | "filename" : "DaysSince_Icon-1024.png", 95 | "idiom" : "ios-marketing", 96 | "scale" : "1x", 97 | "size" : "1024x1024" 98 | } 99 | ], 100 | "info" : { 101 | "author" : "xcode", 102 | "version" : 1 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/AppIcon.appiconset/DaysSince_Icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/DaysSince/Assets.xcassets/AppIcon.appiconset/DaysSince_Icon-1024.png -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/AppIcon.appiconset/DaysSince_Icon-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/DaysSince/Assets.xcassets/AppIcon.appiconset/DaysSince_Icon-20.png -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/AppIcon.appiconset/DaysSince_Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/DaysSince/Assets.xcassets/AppIcon.appiconset/DaysSince_Icon-40.png -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/AppIcon.appiconset/DaysSince_Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/DaysSince/Assets.xcassets/AppIcon.appiconset/DaysSince_Icon-40@2x.png -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/AppIcon.appiconset/DaysSince_Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/DaysSince/Assets.xcassets/AppIcon.appiconset/DaysSince_Icon-60@2x.png -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/AppIcon.appiconset/DaysSince_Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/DaysSince/Assets.xcassets/AppIcon.appiconset/DaysSince_Icon-60@3x.png -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/backgroundColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.925", 9 | "green" : "0.862", 10 | "red" : "0.890" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.770", 27 | "green" : "0.709", 28 | "red" : "0.732" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/healthCategoryIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-health-48.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/healthCategoryIcon.imageset/icons8-health-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/DaysSince/Assets.xcassets/healthCategoryIcon.imageset/icons8-health-48.png -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/healthColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.357", 9 | "green" : "0.678", 10 | "red" : "0.951" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "1.000", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/hobbiesCategoryIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-soccer-ball-50.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/hobbiesCategoryIcon.imageset/icons8-soccer-ball-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/DaysSince/Assets.xcassets/hobbiesCategoryIcon.imageset/icons8-soccer-ball-50.png -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/hobbiesColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.906", 9 | "green" : "0.774", 10 | "red" : "0.432" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "1.000", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/lifeCategoryIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-trust-50 (1).png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/lifeCategoryIcon.imageset/icons8-trust-50 (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/DaysSince/Assets.xcassets/lifeCategoryIcon.imageset/icons8-trust-50 (1).png -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/lifeColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.953", 9 | "green" : "0.475", 10 | "red" : "0.549" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "1.000", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/workCategoryIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-work-64.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/workCategoryIcon.imageset/icons8-work-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordibruin/dayssince/47bff5cd99a9541e470387791200b1cbe745441d/DaysSince/DaysSince/Assets.xcassets/workCategoryIcon.imageset/icons8-work-64.png -------------------------------------------------------------------------------- /DaysSince/DaysSince/Assets.xcassets/workColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x89", 9 | "green" : "0x63", 10 | "red" : "0xE4" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "1.000", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Calendar/RootView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootView.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 4/4/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct RootView: View { 11 | @Environment(\.calendar) var calendar 12 | 13 | private var year: DateInterval { 14 | calendar.dateInterval(of: .year, for: Date())! 15 | } 16 | 17 | var body: some View { 18 | CalendarView(interval: year) { date in 19 | Text("30") 20 | .hidden() 21 | .padding(8) 22 | .background(Color.blue) 23 | .clipShape(Circle()) 24 | .padding(.vertical, 4) 25 | .overlay( 26 | Text(String(self.calendar.component(.day, from: date))) 27 | ) 28 | } 29 | } 30 | } 31 | 32 | struct RootView_Previews: PreviewProvider { 33 | static var previews: some View { 34 | RootView() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 3/28/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | @AppStorage("items") var items = [DaysSinceItem]() 12 | @AppStorage("completedItems") var completedItems = [DaysSinceItem]() 13 | @AppStorage("favoriteItems") var favoriteItems = [DaysSinceItem]() 14 | 15 | var body: some View { 16 | MainScreen(items: $items, completedItems: $completedItems, favoriteItems: $favoriteItems) 17 | } 18 | } 19 | 20 | struct ContentView_Previews: PreviewProvider { 21 | static var previews: some View { 22 | Group { 23 | ContentView() 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/DaysSince.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/DaysSinceApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DaysSinceApp.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 3/28/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct DaysSinceApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Extensions/Array+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Extensions.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 3/29/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Array: RawRepresentable where Element: Codable { 11 | public init?(rawValue: String) { 12 | guard let data = rawValue.data(using: .utf8) else { 13 | return nil 14 | } 15 | do { 16 | let result = try JSONDecoder().decode([Element].self, from: data) 17 | // print("Init from result: \(result)") 18 | self = result 19 | } catch { 20 | print("Error: \(error)") 21 | return nil 22 | } 23 | } 24 | 25 | public var rawValue: String { 26 | guard let data = try? JSONEncoder().encode(self), 27 | let result = String(data: data, encoding: .utf8) 28 | else { 29 | return "[]" 30 | } 31 | print("Returning \(result)") 32 | return result 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Extensions/Binding+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Binding+Extensions.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 5/27/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension Binding { 12 | var optional: Binding { 13 | Binding( 14 | get: { self.wrappedValue }, 15 | set: { 16 | guard let value = $0 else { return } 17 | self.wrappedValue = value 18 | } 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Extensions/Calendar+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Calendar+Extensions.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 3/30/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Calendar { 11 | func numberOfDaysBetween(_ from: Date, and to: Date) -> Int { 12 | let fromDate = startOfDay(for: from) // <1> 13 | let toDate = startOfDay(for: to) // <2> 14 | let numberOfDays = dateComponents([.day], from: fromDate, to: toDate) // <3> 15 | 16 | return numberOfDays.day! 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Extensions/Color+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color+Extensions.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 4/8/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension Color { 12 | static let workColor = Color("workColor") // "ppBlue") 13 | static let lifeColor = Color("lifeColor") // "ppBlue") 14 | static let hobbiesColor = Color("hobbiesColor") 15 | static let healthColor = Color("healthColor") 16 | static let backgroundColor = Color("backgroundColor") 17 | } 18 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSUserActivityTypes 6 | 7 | ConfigurationIntent 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/MainScreen/Views/BottomSection/BottomSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BottomSection.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 5/23/22. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | 11 | struct BottomSection: View { 12 | @Default(.categories) var categories 13 | 14 | @Binding var items: [DSItem] 15 | 16 | @Binding var editItemSheet: Bool 17 | @Binding var tappedItem: DSItem 18 | @Binding var isDaysDisplayModeDetailed: Bool 19 | 20 | var body: some View { 21 | DSItemListView( 22 | items: $items, 23 | editItemSheet: $editItemSheet, 24 | tappedItem: $tappedItem, 25 | isDaysDisplayModeDetailed: $isDaysDisplayModeDetailed 26 | ) 27 | 28 | // Add some space after the items for the button. 29 | Color(.clear) 30 | .frame(height: 100) 31 | .padding(.vertical, 1) 32 | } 33 | } 34 | 35 | struct BottomSection_Previews: PreviewProvider { 36 | static var previews: some View { 37 | BottomSection(items: .constant([.placeholderItem()]), 38 | editItemSheet: .constant(false), 39 | tappedItem: .constant(DSItem.placeholderItem()), 40 | isDaysDisplayModeDetailed: .constant(false)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/MainScreen/Views/MainBackgroundView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainBackgroundView.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 5/23/22. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | 11 | struct MainBackgroundView: View { 12 | @Environment(\.colorScheme) var colorScheme 13 | 14 | @Default(.backgroundColor) var backgroundColor 15 | 16 | var body: some View { 17 | if colorScheme == .dark { 18 | Color(.systemBackground) 19 | .ignoresSafeArea() 20 | } else { 21 | LinearGradient( 22 | gradient: .init(colors: [backgroundColor.opacity(0.6), backgroundColor]), 23 | startPoint: .init(x: 1, y: 0), 24 | endPoint: .init(x: 0.0001, y: 0) 25 | ) 26 | .ignoresSafeArea() 27 | } 28 | } 29 | } 30 | 31 | struct MainBackgroundView_Previews: PreviewProvider { 32 | static var previews: some View { 33 | MainBackgroundView() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Model/CategoryDSItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CategoryDSItem.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 5/25/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | /// Representation of category of the old DS events (before customizable category feature) 12 | enum CategoryDSIte: Codable, Identifiable, Equatable, CaseIterable, Hashable { 13 | static var allCases: [CategoryDSIte] = [ 14 | .work, .life, .hobbies, .health, 15 | ] 16 | 17 | case work 18 | case life 19 | case health 20 | case hobbies 21 | case none 22 | 23 | var name: String { 24 | switch self { 25 | case .work: 26 | return "Work" 27 | case .life: 28 | return "Life" 29 | case .health: 30 | return "Health" 31 | case .hobbies: 32 | return "Hobbies" 33 | case .none: 34 | return "No category" 35 | } 36 | } 37 | 38 | var id: String { 39 | switch self { 40 | case .work: 41 | return "Work" 42 | case .life: 43 | return "Life" 44 | case .health: 45 | return "Health" 46 | case .hobbies: 47 | return "Hobbies" 48 | case .none: 49 | return "No Category" 50 | } 51 | } 52 | 53 | var color: Color { 54 | switch self { 55 | case .work: 56 | return Color.workColor 57 | case .life: 58 | return Color.lifeColor 59 | case .health: 60 | return Color.healthColor 61 | case .hobbies: 62 | return Color.hobbiesColor 63 | case .none: 64 | return Color.black 65 | } 66 | } 67 | 68 | var emoji: String { 69 | switch self { 70 | case .work: 71 | return "workCategoryIcon" 72 | case .life: 73 | return "lifeCategoryIcon" 74 | case .health: 75 | return "healthCategoryIcon" 76 | case .hobbies: 77 | return "hobbiesCategoryIcon" 78 | case .none: 79 | return "" 80 | } 81 | } 82 | 83 | var sfSymbolName: String { 84 | switch self { 85 | case .work: 86 | return "lightbulb" 87 | case .life: 88 | return "leaf" 89 | case .health: 90 | return "heart.text.square" 91 | case .hobbies: 92 | return "gamecontroller" 93 | case .none: 94 | return "" 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Model/DSItemReminders.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DSItemReminders.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 6/3/22. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Reminders for the Days Since events. 11 | enum DSItemReminders: Codable, Equatable, CaseIterable, Hashable { 12 | static var allCases: [DSItemReminders] = [ 13 | .daily, .weekly, .monthly, .none, 14 | ] 15 | 16 | case daily 17 | case weekly 18 | case monthly 19 | case none 20 | 21 | var name: String { 22 | switch self { 23 | case .daily: 24 | return "Daily" 25 | case .weekly: 26 | return "Weekly" 27 | case .monthly: 28 | return "Monthly" 29 | case .none: 30 | return "No reminders" 31 | } 32 | } 33 | 34 | // var dateComponents: DateComponents { 35 | // switch self { 36 | // case .daily: 37 | // dateComponents.hour = 10 38 | // return dateComponents 39 | // case .weekly: 40 | // return dateComponents.day = "Monday" 41 | // case .monthly: 42 | // return dateC 43 | // } 44 | // } 45 | } 46 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Model/Defaults.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Defaults.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 4/7/22. 6 | // 7 | 8 | import Foundation 9 | // import Defaults 10 | import SwiftUI 11 | 12 | // extension Defaults.Keys { 13 | // static let soundEnabled = Key("soundEnabled", default: true, suite: DefaultsConstants.sharedDefaults) 14 | // static let vibrationEnabled = Key("vibrationEnabled", default: true, suite: DefaultsConstants.sharedDefaults) 15 | // static let sensitivity = Key("postureSensitivity", default: .medium, suite: DefaultsConstants.sharedDefaults) 16 | //// static let soundID = Key("soundID", default: SoundId(id: 1052, name: "Ding", emoji: "🛎")) 17 | // 18 | // static let hasSeenOnboarding = Key("hasSeenOnboarding", default: false, suite: DefaultsConstants.sharedDefaults) 19 | // 20 | // 21 | //// static let hasSeenOnboarding = Key("hasSeenOnboarding4444", default: false) 22 | //// static let hasSeenOnboarding = Key("hasSeenOnboarding", default: false, suite: DefaultsConstants.sharedDefaults) 23 | // 24 | // static let goodColor = Key("goodColor", default: .ppBlue, suite: DefaultsConstants.sharedDefaults) 25 | // static let badColor = Key("badColor", default: .ppRed, suite: DefaultsConstants.sharedDefaults) 26 | // 27 | // 28 | // static let superSeriousMode = Key("superSeriousMode", default: false, suite: DefaultsConstants.sharedDefaults) 29 | // static let startSessionOnLaunch = Key("startSessionOnLaunch", default: false, suite: DefaultsConstants.sharedDefaults) 30 | // 31 | // static let startedFromShortcuts = Key("startedFromShortcuts", default: 0, suite: DefaultsConstants.sharedDefaults) 32 | // 33 | // static let soundFrequency = Key("soundFrequency", default: 5, suite: DefaultsConstants.sharedDefaults) 34 | // 35 | // 36 | // // MARK: Customisation 37 | // static let alertDelay = Key("alertDelay", default: 0, suite: DefaultsConstants.sharedDefaults) 38 | // 39 | // } 40 | -------------------------------------------------------------------------------- /DaysSince/DaysSince/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DaysSince/DaysSinceApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DaysSinceApp.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 3/28/22. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | import WidgetKit 11 | import TelemetryDeck 12 | 13 | @main 14 | struct DaysSinceApp: App { 15 | @StateObject var notificationManager = NotificationManager() 16 | @StateObject var categoryManager = CategoryManager() 17 | @StateObject var reviewManager: ReviewManager 18 | 19 | init() { 20 | let reviewManager = ReviewManager() 21 | _reviewManager = StateObject(wrappedValue: reviewManager) 22 | let config = TelemetryDeck.Config(appID: "FBE58244-22B0-4207-9ED7-052DEB5B8A26") 23 | config.defaultSignalPrefix = "DaysSince." 24 | config.testMode = isSimulatorOrTestFlight() 25 | TelemetryDeck.initialize(config: config) 26 | // Analytics.send(.launchApp) 27 | } 28 | 29 | @Environment(\.scenePhase) var scenePhase 30 | 31 | var body: some Scene { 32 | WindowGroup { 33 | ContentView() 34 | .environmentObject(notificationManager) 35 | .environmentObject(categoryManager) 36 | .environmentObject(reviewManager) 37 | .onChange(of: self.scenePhase) { 38 | switch $0 { 39 | case .background: 40 | WidgetCenter.shared.reloadAllTimelines() 41 | default: break 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /DaysSince/Extensions/Array+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Extensions.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 3/29/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Array: RawRepresentable where Element: Codable { 11 | public init?(rawValue: String) { 12 | guard let data = rawValue.data(using: .utf8) else { 13 | return nil 14 | } 15 | do { 16 | let result = try JSONDecoder().decode([Element].self, from: data) 17 | self = result 18 | } catch { 19 | print("Error: \(error)") 20 | return nil 21 | } 22 | } 23 | 24 | public var rawValue: String { 25 | guard let data = try? JSONEncoder().encode(self), 26 | let result = String(data: data, encoding: .utf8) 27 | else { 28 | return "[]" 29 | } 30 | return result 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /DaysSince/Extensions/Bundle+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bundle+Extensions.swift 3 | // DaysSince 4 | // 5 | // Created by Victoria Petrova on 05/04/2025. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Bundle { 11 | var releaseVersionNumber: String? { 12 | return infoDictionary?["CFBundleShortVersionString"] as? String 13 | } 14 | var buildVersionNumber: String? { 15 | return infoDictionary?["CFBundleVersion"] as? String 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /DaysSince/Extensions/Calendar+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Calendar+Extensions.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 3/30/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Calendar { 11 | func numberOfDaysBetween(_ from: Date, and to: Date) -> Int { 12 | let fromDate = startOfDay(for: from) // <1> 13 | let toDate = startOfDay(for: to) // <2> 14 | let numberOfDays = dateComponents([.day], from: fromDate, to: toDate) // <3> 15 | 16 | return numberOfDays.day! 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DaysSince/Extensions/Color+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color+Extensions.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 4/8/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | // Custom colors used thoughout the app 12 | extension Color { 13 | static let workColor = Color("workColor") 14 | static let lifeColor = Color("lifeColor") 15 | static let hobbiesColor = Color("hobbiesColor") 16 | static let healthColor = Color("healthColor") 17 | static let backgroundColor = Color("backgroundColor") 18 | static let peachLightPink = Color("peachLightPink") 19 | static let peachDarkPink = Color("peachDarkPink") 20 | static let marioBlue = Color("marioBlue") 21 | static let marioRed = Color("marioRed") 22 | static let zeldaGreen = Color("zeldaGreen") 23 | static let zeldaYellow = Color("zeldaYellow") 24 | static let animalCrossingsBrown = Color("animalCrossingsBrown") 25 | static let animalCrossingsGreen = Color("animalCrossingsGreen") 26 | 27 | public func lighter(by amount: CGFloat = 0.2) -> Self { Self(UIColor(self).lighter(by: amount)) } 28 | public func darker(by amount: CGFloat = 0.2) -> Self { Self(UIColor(self).darker(by: amount)) } 29 | } 30 | 31 | // Used for color comparison of the themes. 32 | extension UIColor { 33 | func mix(with color: UIColor, amount: CGFloat) -> Self { 34 | var red1: CGFloat = 0 35 | var green1: CGFloat = 0 36 | var blue1: CGFloat = 0 37 | var alpha1: CGFloat = 0 38 | 39 | var red2: CGFloat = 0 40 | var green2: CGFloat = 0 41 | var blue2: CGFloat = 0 42 | var alpha2: CGFloat = 0 43 | 44 | getRed(&red1, green: &green1, blue: &blue1, alpha: &alpha1) 45 | color.getRed(&red2, green: &green2, blue: &blue2, alpha: &alpha2) 46 | 47 | return Self( 48 | red: red1 * CGFloat(1.0 - amount) + red2 * amount, 49 | green: green1 * CGFloat(1.0 - amount) + green2 * amount, 50 | blue: blue1 * CGFloat(1.0 - amount) + blue2 * amount, 51 | alpha: alpha1 52 | ) 53 | } 54 | 55 | func lighter(by amount: CGFloat = 0.2) -> Self { mix(with: .white, amount: amount) } 56 | func darker(by amount: CGFloat = 0.2) -> Self { mix(with: .black, amount: amount) } 57 | } 58 | -------------------------------------------------------------------------------- /DaysSince/Extensions/Date+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+Extensions.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 2/22/24. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Date { 11 | var dayBefore: Date { 12 | return Calendar.current.date(byAdding: .day, value: -1, to: self)! 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /DaysSince/Extensions/Defaults+Extension+Colors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Defaults+Extension+Colors.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 9/24/23. 6 | // 7 | 8 | import Defaults 9 | import Foundation 10 | import SwiftUI 11 | 12 | extension Defaults.Keys { 13 | static let mainColor = Key("mainColor", default: Color.workColor) 14 | static let backgroundColor = Key("backgroundColor", default: Color.backgroundColor) 15 | static let selectedThemeId = Key("selectedThemeId", default: "default") 16 | 17 | static let categories = Key<[Category]>("categories", default: [ 18 | Category(name: "Work", emoji: "lightbulb", color: .work), 19 | Category(name: "Life", emoji: "leaf", color: .life), 20 | Category(name: "Hobby", emoji: "gamecontroller", color: .hobbies), 21 | Category(name: "Health", emoji: "heart.text.square", color: .health)]) 22 | } 23 | -------------------------------------------------------------------------------- /DaysSince/Extensions/UIApplication+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+Extensions.swift 3 | // DaysSince 4 | // 5 | // Created by Saurabh Jamadagni on 03/03/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension UIApplication { 11 | func endEditing() { 12 | sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) 13 | } 14 | 15 | static var appVersion: String? { 16 | return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DaysSince/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ITSAppUsesNonExemptEncryption 6 | 7 | NSUserActivityTypes 8 | 9 | SelectEventIntent 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /DaysSince/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | DaysSince 4 | 5 | Created by Michael Tigas on 9/11/2022. 6 | 7 | */ 8 | 9 | "widget.singleEvent.title" = "Event Widget"; 10 | "widget.singleEvent.explanation" = "Display the details of an event."; 11 | "widget.multiEvent.title" = "Multiple Events Widget"; 12 | "widget.multiEvent.explanation" = "Display the details of up to 5 events."; 13 | -------------------------------------------------------------------------------- /DaysSince/Managers/ReviewManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReviewManager.swift 3 | // DaysSince 4 | // 5 | // Created by Victoria Petrova on 05/04/2025. 6 | // 7 | 8 | import Foundation 9 | import StoreKit 10 | import SwiftUI 11 | 12 | class ReviewManager: ObservableObject { 13 | 14 | @AppStorage("latestVersionThatReviewWasAskedFor") var latestVersionThatReviewWasAskedFor: String = "1.0" 15 | 16 | func promptReviewAlert() { 17 | guard let currentVersion = UIApplication.appVersion else { 18 | print("Couldn't get app version") 19 | return 20 | } 21 | 22 | print("Current version: \(currentVersion), Last asked version: \(latestVersionThatReviewWasAskedFor)") 23 | 24 | if currentVersion == latestVersionThatReviewWasAskedFor { 25 | print("Already asked for this version, do nothing") 26 | return 27 | } 28 | 29 | if let scene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene { 30 | print("Requesting review for version \(currentVersion)") 31 | SKStoreReviewController.requestReview(in: scene) 32 | 33 | Analytics.send(.reviewPrompt) 34 | } 35 | 36 | latestVersionThatReviewWasAskedFor = currentVersion 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/Model/AlternativeIcon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlternativeIcon.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 6/29/22. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represent alternative app icons for the app. 11 | struct AlternativeIcon: Identifiable { 12 | var id: String { 13 | return name + iconName 14 | } 15 | 16 | let name: String 17 | let iconName: String 18 | let premium: Bool 19 | 20 | let original: Bool 21 | } 22 | -------------------------------------------------------------------------------- /DaysSince/Model/Category.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Category.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 11/22/23. 6 | // 7 | 8 | import Defaults 9 | import Foundation 10 | import SwiftUI 11 | 12 | /// Category object that a Days Since event/item is categorized into. 13 | struct Category: Identifiable, Codable, Equatable, Defaults.Serializable { 14 | let id: UUID 15 | var name: String 16 | var emoji: String 17 | var color: CategoryColor 18 | 19 | init(id: UUID = UUID(), name: String, emoji: String, color: CategoryColor) { 20 | self.id = id 21 | self.name = name 22 | self.emoji = emoji 23 | self.color = color 24 | } 25 | 26 | static func == (lhs: Category, rhs: Category) -> Bool { 27 | return lhs.id == rhs.id 28 | } 29 | 30 | // Computed property for a hashable identifier 31 | var hashableIdentifier: String { 32 | return "\(id)-\(name)-\(emoji)-\(color)" 33 | } 34 | 35 | static func placeholderCategory() -> Category { 36 | return Category(name: "Placeholder", emoji: "placeholder", color: .work) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DaysSince/Model/CategoryColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CategoryColor.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 11/22/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | /// Represents the color of a category. Used to store in user defaults. 12 | enum CategoryColor: Codable, Identifiable, Equatable, CaseIterable, Hashable { 13 | static var allCases: [CategoryColor] = [ 14 | .work, .life, .hobbies, .health, .marioBlue, .zeldaYellow, .animalCrossingsGreen, .marioRed, .animalCrossingsBrown, .black 15 | ] 16 | 17 | case work 18 | case life 19 | case health 20 | case hobbies 21 | case marioBlue 22 | case zeldaYellow 23 | case animalCrossingsGreen 24 | case marioRed 25 | case animalCrossingsBrown 26 | case black 27 | 28 | var id: String { 29 | switch self { 30 | case .work: 31 | return "Work" 32 | case .life: 33 | return "Life" 34 | case .health: 35 | return "Health" 36 | case .hobbies: 37 | return "Hobby" 38 | case .marioBlue: 39 | return "MarioBlue" 40 | case .zeldaYellow: 41 | return "ZeldaYellow" 42 | case .animalCrossingsGreen: 43 | return "AnimalCrossingsGreen" 44 | case .marioRed: 45 | return "MarioRed" 46 | case .animalCrossingsBrown: 47 | return "AnimalCrossingsBrown" 48 | case .black: 49 | return "Black" 50 | } 51 | } 52 | 53 | var color: Color { 54 | switch self { 55 | case .work: 56 | return Color.workColor 57 | case .life: 58 | return Color.lifeColor 59 | case .health: 60 | return Color.healthColor 61 | case .hobbies: 62 | return Color.hobbiesColor 63 | case .marioBlue: 64 | return Color.marioBlue 65 | case .zeldaYellow: 66 | return Color.zeldaYellow 67 | case .animalCrossingsGreen: 68 | return Color.animalCrossingsGreen 69 | case .marioRed: 70 | return Color.marioRed 71 | case .animalCrossingsBrown: 72 | return Color.animalCrossingsBrown 73 | case .black: 74 | return Color.black 75 | } 76 | } 77 | 78 | func foregroundColor(for colorScheme: ColorScheme) -> Color { 79 | if self == .black && colorScheme == .dark { 80 | return Color.white 81 | } else { 82 | return self.color 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /DaysSince/Model/Defaults.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Defaults.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 4/7/22. 6 | // 7 | 8 | import Foundation 9 | // import Defaults 10 | import SwiftUI 11 | 12 | // extension Defaults.Keys { 13 | // static let soundEnabled = Key("soundEnabled", default: true, suite: DefaultsConstants.sharedDefaults) 14 | // static let vibrationEnabled = Key("vibrationEnabled", default: true, suite: DefaultsConstants.sharedDefaults) 15 | // static let sensitivity = Key("postureSensitivity", default: .medium, suite: DefaultsConstants.sharedDefaults) 16 | //// static let soundID = Key("soundID", default: SoundId(id: 1052, name: "Ding", emoji: "🛎")) 17 | // 18 | // static let hasSeenOnboarding = Key("hasSeenOnboarding", default: false, suite: DefaultsConstants.sharedDefaults) 19 | // 20 | // 21 | //// static let hasSeenOnboarding = Key("hasSeenOnboarding4444", default: false) 22 | //// static let hasSeenOnboarding = Key("hasSeenOnboarding", default: false, suite: DefaultsConstants.sharedDefaults) 23 | // 24 | // static let goodColor = Key("goodColor", default: .ppBlue, suite: DefaultsConstants.sharedDefaults) 25 | // static let badColor = Key("badColor", default: .ppRed, suite: DefaultsConstants.sharedDefaults) 26 | // 27 | // 28 | // static let superSeriousMode = Key("superSeriousMode", default: false, suite: DefaultsConstants.sharedDefaults) 29 | // static let startSessionOnLaunch = Key("startSessionOnLaunch", default: false, suite: DefaultsConstants.sharedDefaults) 30 | // 31 | // static let startedFromShortcuts = Key("startedFromShortcuts", default: 0, suite: DefaultsConstants.sharedDefaults) 32 | // 33 | // static let soundFrequency = Key("soundFrequency", default: 5, suite: DefaultsConstants.sharedDefaults) 34 | // 35 | // 36 | // // MARK: Customisation 37 | // static let alertDelay = Key("alertDelay", default: 0, suite: DefaultsConstants.sharedDefaults) 38 | // 39 | // } 40 | -------------------------------------------------------------------------------- /DaysSince/Onboarding/OnboardingScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnboardingScreen.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 7/13/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct OnboardingScreen: View { 11 | @Binding var hasSeenOnboarding: Bool 12 | @Binding var items: [DSItem] 13 | 14 | @State var selectedPage = 0 15 | 16 | var body: some View { 17 | TabView(selection: $selectedPage) { 18 | Introduction(selectedPage: $selectedPage).tag(0) 19 | .simultaneousGesture(DragGesture()) 20 | 21 | CreateFirstEvent(hasSeenOnboarding: $hasSeenOnboarding, selectedPage: $selectedPage, items: $items).tag(1) 22 | .simultaneousGesture(DragGesture()) 23 | 24 | // PurchasesView(inOnboarding: true, selectedPage: $selectedPage).tag(2) 25 | // .clipped() 26 | // .simultaneousGesture(DragGesture()) 27 | // 28 | // Settings().tag(3) 29 | // .simultaneousGesture(DragGesture()) 30 | } 31 | .tabViewStyle(.page(indexDisplayMode: .never)) 32 | .interactiveDismissDisabled() 33 | .edgesIgnoringSafeArea(.all) 34 | // .statusBarStyle(.lightContent) 35 | } 36 | } 37 | 38 | struct OnboardingScreen_Previews: PreviewProvider { 39 | static var previews: some View { 40 | OnboardingScreen(hasSeenOnboarding: .constant(false), items: .constant([])) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DaysSince/Onboarding/Pages/AnimateText.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimateText.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 7/13/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AnimateText: Identifiable { 11 | var id = UUID().uuidString 12 | var text: String 13 | var offset: CGFloat = 110 14 | } 15 | -------------------------------------------------------------------------------- /DaysSince/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DaysSince/Settings/ColorThemeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorThemeView.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 9/24/23. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | 11 | struct ColorThemeView: View { 12 | @Environment(\.colorScheme) var colorScheme 13 | @EnvironmentObject var reviewManager: ReviewManager 14 | 15 | @Default(.mainColor) var mainColor 16 | @Default(.backgroundColor) var backgroundColor 17 | @Default(.selectedThemeId) var selectedThemeId 18 | 19 | let mainColorTemporary: Color 20 | let backgroundColorTemporary: Color 21 | let themeId: String 22 | 23 | var isThemeSelected: Bool { 24 | return themeId == selectedThemeId 25 | } 26 | 27 | var body: some View { 28 | Circle() 29 | .frame(width: 72, height: 72) 30 | .overlay { 31 | LinearGradient(stops: [ 32 | Gradient.Stop(color: backgroundColorTemporary, location: 0), 33 | Gradient.Stop(color: backgroundColorTemporary, location: 0.5), 34 | Gradient.Stop(color: mainColorTemporary, location: 0.5), 35 | Gradient.Stop(color: mainColorTemporary, location: 1), 36 | ], startPoint: .topLeading, endPoint: .bottomTrailing) 37 | } 38 | .overlay( 39 | RoundedRectangle(cornerRadius: 24) 40 | .stroke(colorScheme == .light ? .black : .white, lineWidth: isThemeSelected ? 6 : 0) 41 | .background(isThemeSelected ? .clear : .black.opacity(0.5)) 42 | ) 43 | .clipShape(RoundedRectangle(cornerRadius: 24)) 44 | .onTapGesture { 45 | Analytics.send(.chooseTheme, with: ["themeId": themeId]) 46 | 47 | let generator = UIImpactFeedbackGenerator(style: .medium) 48 | generator.impactOccurred() 49 | 50 | withAnimation { 51 | selectedThemeId = themeId 52 | mainColor = mainColorTemporary 53 | backgroundColor = backgroundColorTemporary 54 | } 55 | 56 | reviewManager.promptReviewAlert() 57 | } 58 | .transition(.opacity) 59 | } 60 | 61 | // MARK: - Actions 62 | 63 | func colorEquals(_ color1: Color, _ color2: Color) -> Bool { 64 | let uiColor1 = UIColor(color1) 65 | let uiColor2 = UIColor(color2) 66 | 67 | return uiColor1 == uiColor2 68 | } 69 | } 70 | 71 | struct ColorThemeView_Previews: PreviewProvider { 72 | static var previews: some View { 73 | ColorThemeView(mainColorTemporary: Color.workColor, backgroundColorTemporary: Color.backgroundColor, themeId: "default") 74 | ColorThemeView(mainColorTemporary: Color.workColor, backgroundColorTemporary: Color.backgroundColor, themeId: "default") 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /DaysSince/Settings/DetailedTimeDisplayMode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailedTimeDisplayMode.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 7/26/22. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | 11 | struct DetailedTimeDisplayModeCell: View { 12 | @Binding var isDaysDisplayModeDetailed: Bool 13 | 14 | @Default(.mainColor) var mainColor 15 | 16 | var body: some View { 17 | Section { 18 | HStack { 19 | buttonImage 20 | buttonText 21 | Spacer() 22 | toggle 23 | } 24 | } footer: { 25 | Text("Display the number of years, months, and days since an event") 26 | .font(Font.system(.body, design: .rounded)) 27 | .fixedSize(horizontal: false, vertical: true) 28 | .padding(.leading, -8) 29 | } 30 | } 31 | 32 | var buttonImage: some View { 33 | LinearGradient(colors: [mainColor, mainColor.lighter()], startPoint: .topLeading, endPoint: .bottomTrailing) 34 | .frame(width: 30, height: 30) 35 | .cornerRadius(8) 36 | .overlay( 37 | Image(systemName: "rectangle.and.pencil.and.ellipsis") 38 | .symbolRenderingMode(.hierarchical) 39 | .foregroundColor(.white) 40 | ) 41 | .padding(.leading, -10) 42 | } 43 | 44 | var buttonText: some View { 45 | // TODO: Current wording is poor. 46 | Text("Detailed Time Display Mode") 47 | .font(.system(.body, design: .rounded)) 48 | } 49 | 50 | var toggle: some View { 51 | Toggle("Detailed Time Display Mode", isOn: $isDaysDisplayModeDetailed) 52 | .tint(mainColor) 53 | .labelsHidden() 54 | .onChange(of: isDaysDisplayModeDetailed) { isDaysDisplayModeDetailed in 55 | if isDaysDisplayModeDetailed { 56 | Analytics.send(.detailedModeOn) 57 | } 58 | } 59 | 60 | } 61 | } 62 | 63 | struct DetailedTimeDisplayMode_Previews: PreviewProvider { 64 | static var previews: some View { 65 | DetailedTimeDisplayModeCell(isDaysDisplayModeDetailed: .constant(false)) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /DaysSince/Settings/LinkButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FollowButton.swift 3 | // DaysSince 4 | // 5 | // Created by Victoria Petrova on 03/05/2025. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | 11 | 12 | struct TwitterButton: View { 13 | var body: some View { 14 | LinkButton( 15 | title: "Follow on Twitter", 16 | symbolName: "bird.fill", 17 | urlString: "https://twitter.com/h" 18 | ) 19 | } 20 | } 21 | 22 | struct WebsiteButton: View { 23 | var body: some View { 24 | LinkButton( 25 | title: "Website", 26 | symbolName: "globe", 27 | urlString: "https://dayssince-website.vercel.app/" 28 | ) 29 | } 30 | } 31 | 32 | struct PrivacyButton: View { 33 | var body: some View { 34 | LinkButton( 35 | title: "Privacy Policy", 36 | symbolName: "lock.fill", 37 | urlString: "https://dayssince-website.vercel.app/#privacy" 38 | ) 39 | } 40 | } 41 | 42 | struct LinkButton: View { 43 | @Default(.mainColor) var mainColor 44 | @Default(.selectedThemeId) var selectedThemeId 45 | @Environment(\.openURL) var openURL 46 | @Environment(\.colorScheme) var colorScheme 47 | 48 | let title: String 49 | let symbolName: String 50 | let urlString: String 51 | 52 | var body: some View { 53 | Button { 54 | if let url = URL(string: urlString) { 55 | openURL(url) 56 | } 57 | } label: { 58 | HStack { 59 | buttonImage 60 | buttonText 61 | Spacer() 62 | buttonShareArrow 63 | } 64 | } 65 | .foregroundColor(.primary) 66 | } 67 | 68 | var buttonImage: some View { 69 | LinearGradient(colors: [mainColor, mainColor.lighter()], startPoint: .topLeading, endPoint: .bottomTrailing) 70 | .frame(width: 34, height: 34) 71 | .cornerRadius(8) 72 | .overlay( 73 | Image(systemName: symbolName) 74 | .symbolRenderingMode(.hierarchical) 75 | .foregroundColor(.white) 76 | ) 77 | .padding(.leading, -10) 78 | } 79 | 80 | var buttonText: some View { 81 | Text(title) 82 | .font(.system(.body, design: .rounded)) 83 | } 84 | 85 | var buttonShareArrow: some View { 86 | Image(systemName: "arrow.up.forward.app.fill") 87 | .font(.title2) 88 | .foregroundColor(["blackWhite", "zelda", "blackDefaultBackground"].contains(selectedThemeId) ? .primary : mainColor) 89 | .opacity(0.5) 90 | } 91 | } 92 | 93 | struct LinkButton_Preview: PreviewProvider { 94 | static var previews: some View { 95 | LinkButton(title: "Follow on Twitter", 96 | symbolName: "bird.fill", 97 | urlString: "https://twitter.com/YourAppHandle") 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /DaysSince/Settings/MainAppColorPicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainAppColorPicker.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 9/24/23. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | 11 | struct MainAppColorPicker: View { 12 | @Default(.mainColor) var mainColor 13 | 14 | var body: some View { 15 | Section { 16 | HStack { 17 | buttonImage 18 | buttonText 19 | Spacer() 20 | colorPicker 21 | } 22 | } 23 | } 24 | 25 | var buttonImage: some View { 26 | LinearGradient(colors: [Color.workColor, Color.workColor.lighter()], startPoint: .topLeading, endPoint: .bottomTrailing) 27 | .frame(width: 30, height: 30) 28 | .cornerRadius(8) 29 | .overlay( 30 | Image(systemName: "rectangle.and.pencil.and.ellipsis") 31 | .symbolRenderingMode(.hierarchical) 32 | .foregroundColor(.white) 33 | ) 34 | .padding(.leading, -10) 35 | } 36 | 37 | var buttonText: some View { 38 | Text("Customize the color of the app") 39 | .font(.system(.body, design: .rounded)) 40 | } 41 | 42 | var colorPicker: some View { 43 | ColorPicker("Set the main app color", selection: $mainColor) 44 | .labelsHidden() 45 | } 46 | } 47 | 48 | struct MainAppColorPicker_Previews: PreviewProvider { 49 | static var previews: some View { 50 | MainAppColorPicker() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /DaysSince/Settings/SettingsReviewButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsReviewButton.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 6/30/22. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | 11 | struct SettingsReviewButton: View { 12 | @Environment(\.openURL) var openURL 13 | @Default(.mainColor) var mainColor 14 | @Default(.selectedThemeId) var selectedThemeId 15 | 16 | var body: some View { 17 | Button { 18 | Analytics.send(.settingsReview) 19 | if let url = URL(string: "https://apps.apple.com/us/app/days-since-track-memories/id1634218216?action=write-review") { 20 | openURL(url) 21 | } 22 | } label: { 23 | HStack { 24 | buttonImage 25 | buttonText 26 | Spacer() 27 | buttonShareArrow 28 | } 29 | } 30 | .foregroundColor(.primary) 31 | } 32 | 33 | var buttonImage: some View { 34 | LinearGradient(colors: [mainColor, mainColor.lighter()], startPoint: .topLeading, endPoint: .bottomTrailing) 35 | .frame(width: 34, height: 34) 36 | .cornerRadius(8) 37 | .overlay( 38 | Image(systemName: "star.fill") 39 | .symbolRenderingMode(.hierarchical) 40 | .foregroundColor(.white) 41 | ) 42 | .padding(.leading, -10) 43 | } 44 | 45 | var buttonText: some View { 46 | Text("Review Days Since") 47 | .font(.system(.body, design: .rounded)) 48 | } 49 | 50 | var buttonShareArrow: some View { 51 | Image(systemName: "arrow.up.forward.app.fill") 52 | .font(.title2) 53 | .foregroundColor(["blackWhite", "zelda", "blackDefaultBackground"].contains(selectedThemeId) ? .primary : mainColor) 54 | .opacity(0.5) 55 | } 56 | } 57 | 58 | struct SettingsReviewButton_Previews: PreviewProvider { 59 | static var previews: some View { 60 | SettingsReviewButton() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /DaysSince/Settings/ShareButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShareButton.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 6/30/22. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | 11 | struct ShareButton: View { 12 | @State var showShare = false 13 | 14 | @Default(.selectedThemeId) var selectedThemeId 15 | @Default(.mainColor) var mainColor 16 | 17 | var body: some View { 18 | Button { 19 | showShare = true 20 | } label: { 21 | HStack { 22 | buttonImage 23 | buttonText 24 | Spacer() 25 | buttonShareArrow 26 | } 27 | } 28 | .foregroundColor(.primary) 29 | .sheet(isPresented: $showShare) { 30 | ShareSheet(items: [URL(string: "https://apps.apple.com/app/id1634218216")!]) 31 | } 32 | } 33 | 34 | var buttonImage: some View { 35 | LinearGradient(colors: [mainColor, mainColor.lighter()], startPoint: .topLeading, endPoint: .bottomTrailing) 36 | .frame(width: 34, height: 34) 37 | .cornerRadius(8) 38 | .overlay( 39 | Image(systemName: "heart.fill") 40 | .symbolRenderingMode(.hierarchical) 41 | .foregroundColor(.white) 42 | ) 43 | .padding(.leading, -10) 44 | } 45 | 46 | var buttonText: some View { 47 | Text("Share Days Since") 48 | .font(.system(.body, design: .rounded)) 49 | } 50 | 51 | var buttonShareArrow: some View { 52 | Image(systemName: "arrow.up.forward.app.fill") 53 | .font(.title2) 54 | .foregroundColor(["blackWhite", "zelda", "blackDefaultBackground"].contains(selectedThemeId) ? .primary : mainColor) 55 | .opacity(0.5) 56 | } 57 | } 58 | 59 | struct ShareButton_Previews: PreviewProvider { 60 | static var previews: some View { 61 | ShareButton() 62 | } 63 | } 64 | 65 | struct ShareSheet: UIViewControllerRepresentable { 66 | var items: [Any] 67 | 68 | func makeUIViewController(context _: Context) -> some UIActivityViewController { 69 | let controller = UIActivityViewController(activityItems: items, applicationActivities: nil) 70 | return controller 71 | } 72 | 73 | func updateUIViewController(_: UIViewControllerType, context _: Context) {} 74 | } 75 | -------------------------------------------------------------------------------- /DaysSince/Settings/SupportButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SupportButton.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 6/30/22. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | 11 | struct SupportButton: View { 12 | @Default(.mainColor) var mainColor 13 | 14 | var body: some View { 15 | NavigationLink { 16 | SupportScreen() 17 | .interactiveDismissDisabled() 18 | } label: { 19 | HStack { 20 | LinearGradient(colors: [mainColor, mainColor.lighter()], startPoint: .topLeading, endPoint: .bottomTrailing) 21 | .frame(width: 34, height: 34) 22 | .cornerRadius(8) 23 | .overlay( 24 | Image(systemName: "questionmark") 25 | .foregroundColor(.white) 26 | .bold() 27 | ) 28 | .padding(.leading, -10) 29 | 30 | text 31 | } 32 | } 33 | } 34 | 35 | var text: some View { 36 | Text("Support") 37 | .font(.system(.body, design: .rounded)) 38 | } 39 | } 40 | 41 | struct SupportButton_Previews: PreviewProvider { 42 | static var previews: some View { 43 | SupportButton() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /DaysSince/Settings/ThemeButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThemeButton.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 9/24/23. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | 11 | struct ThemeButton: View { 12 | @Default(.mainColor) var mainColor 13 | @Binding var showSettings: Bool 14 | @Binding var showThemeSheet: Bool 15 | 16 | var body: some View { 17 | Button { 18 | showSettings = false 19 | showThemeSheet = true 20 | } label: { 21 | HStack { 22 | buttonImage 23 | buttonText 24 | Spacer() 25 | Image(systemName: "chevron.right") 26 | .symbolRenderingMode(.hierarchical) 27 | .foregroundColor(.gray) 28 | } 29 | } 30 | .foregroundColor(.primary) 31 | } 32 | 33 | var buttonImage: some View { 34 | LinearGradient(colors: [mainColor, mainColor.lighter()], startPoint: .topLeading, endPoint: .bottomTrailing) 35 | .frame(width: 30, height: 30) 36 | .cornerRadius(8) 37 | .overlay( 38 | Image(systemName: "paintpalette.fill") 39 | .symbolRenderingMode(.hierarchical) 40 | .foregroundColor(.white) 41 | ) 42 | .padding(.leading, -10) 43 | } 44 | 45 | var buttonText: some View { 46 | Text("Customize theme") 47 | .font(.system(.body, design: .rounded)) 48 | } 49 | } 50 | 51 | struct MainAppColorPicker_Previews: PreviewProvider { 52 | static var previews: some View { 53 | ThemeButton(showSettings: .constant(true), showThemeSheet: .constant(false)) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /DaysSince/Settings/WishKitView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WishKitView.swift 3 | // DaysSince 4 | // 5 | // Created by Vicki Minerva on 12/2/23. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | import WishKit 11 | 12 | struct WishKitView: View { 13 | @Default(.mainColor) var mainColor 14 | 15 | var body: some View { 16 | NavigationLink { 17 | WishKit.view 18 | } label: { 19 | HStack { 20 | LinearGradient(colors: [mainColor, mainColor.lighter()], startPoint: .topLeading, endPoint: .bottomTrailing) 21 | .frame(width: 34, height: 34) 22 | .cornerRadius(8) 23 | .overlay( 24 | Image(systemName: "lightbulb.fill") 25 | .foregroundColor(.white) 26 | ) 27 | .padding(.leading, -10) 28 | 29 | text 30 | } 31 | } 32 | } 33 | 34 | var text: some View { 35 | Text("Suggest Features") 36 | .font(.system(.body, design: .rounded)) 37 | } 38 | } 39 | 40 | struct WishKitView_Previews: PreviewProvider { 41 | static var previews: some View { 42 | WishKitView() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /DaysSince/Supporter/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.2 2 | - FAQ sections can now be made grouped by setting "hasDetailPage" to true. If so, the first three FAQ items will be displayed, along a view all button. The text in this button can be localized per JSON as well through the "viewAllText" attribute of the JSON string. 3 | - The JSON parser is more forgiving for any missing parts 4 | - Improved theme color support on dark mode 5 | - Support for multiple contact sections with their own localized titles. You can still use the original contactItems style (however the title of this section is always the same so I would suggest using the new one instead) 6 | - Support for a changelog message that shows up if the user has not viewed it yet. You can also show a button that always links to the changelog if you want. 7 | - Lots of code cleanup 8 | - The support page now shows a loading indicator if the JSON has not been retrieved yet. 9 | 10 | ## 1.1 11 | - Small changes based on initial feedback 12 | 13 | ## 1.0 14 | - Initial release 15 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Deeplink.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Deeplink.swift 3 | // Posture Pal 4 | // 5 | // Created by Jordi Bruin on 04/03/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct Deeplink: View { 11 | let id: Int 12 | 13 | @State var item: SupportPageable? 14 | @Environment(\.dismiss) var dismiss 15 | @State var activePage: Int = 0 16 | @StateObject var support = SupportFetcher() 17 | 18 | var body: some View { 19 | NavigationView { 20 | Group { 21 | if item != nil { 22 | tabView() 23 | } else { 24 | VStack { 25 | ProgressView() 26 | Text("Loading") 27 | } 28 | } 29 | } 30 | .navigationBarTitleDisplayMode(.inline) 31 | .toolbar(content: { 32 | ToolbarItem(placement: .navigationBarTrailing) { 33 | Button { 34 | dismiss() 35 | } label: { 36 | Image(systemName: "xmark.circle.fill") 37 | } 38 | .font(.title3) 39 | .foregroundColor(.primary) 40 | .opacity(0.7) 41 | } 42 | }) 43 | } 44 | .tabViewStyle(.page(indexDisplayMode: .always)) 45 | .indexViewStyle(.page(backgroundDisplayMode: .always)) 46 | .onChange(of: support.retrievedSupport, perform: { _ in 47 | if let item = support.allItems.first(where: { $0.id == self.id }) { 48 | self.item = item 49 | } else { 50 | print("ID NOT FOUND! Dismiss and throwerror ") 51 | // Analytics().hit(.openedSupport(id)) 52 | dismiss() 53 | } 54 | }) 55 | } 56 | 57 | @ViewBuilder 58 | func tabView() -> some View { 59 | if item!.supportPages.isEmpty { 60 | Color.clear 61 | } else { 62 | TabView(selection: $activePage) { 63 | ForEach(item!.supportPages) { page in 64 | SupportItemPageView(page: page) 65 | } 66 | } 67 | .background(Color(.systemBackground).edgesIgnoringSafeArea(.bottom)) 68 | } 69 | } 70 | } 71 | 72 | struct Deeplink_Previews: PreviewProvider { 73 | static var previews: some View { 74 | Deeplink(id: 4) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Design/Design.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Design.swift 3 | // SnoozeSupport 4 | // 5 | // Created by Jordi Bruin on 31/12/2021. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct Design { 12 | // MARK: DEFAULTS 13 | 14 | // The background color for the entire support screen 15 | // Note: right now this requires a hack by uncommenting the init in 16 | // Supportscreen. Has not been tested well enough yet and may 17 | // have side effects in other parts of your app 18 | // static let listBackgroundColor: Color = Color(.systemGroupedBackground) 19 | 20 | // The color for the detail page background 21 | // static let backgroundColor: Color = Color(.systemBackground) 22 | 23 | // The colors of the individual cells in the list 24 | // static let listRowBackgroundColor: Color = Color(.systemBackground) 25 | 26 | // The color used for the titles in the items on the support screen 27 | // static let listTitleColor: Color = .primary 28 | 29 | // The color used for the subtitle in the highlighteditem view 30 | // as well as the text in the Toggle Dropdowns for FAQ items 31 | // static let listTextColor: Color = .primary 32 | 33 | // ---------------------- 34 | // CUSTOMIZE COLORS BELOW 35 | // ---------------------- 36 | 37 | // The background color for the entire support screen 38 | // Note: right now this requires a hack by uncommenting the init in 39 | // Supportscreen. Has not been tested well enough yet and may 40 | // have side effects in other parts of your app 41 | static let listBackgroundColor: Color = .init(.systemGroupedBackground) 42 | 43 | // The color for the detail page background 44 | static let backgroundColor: Color = .init(.systemBackground) 45 | 46 | // The colors of the individual cells in the list 47 | static let listRowBackgroundColor: Color = .init(.secondarySystemGroupedBackground) 48 | 49 | // The color used for the titles in the items on the support screen 50 | static let listTitleColor: Color = .primary 51 | 52 | // The color used for the subtitle in the highlighteditem view 53 | // as well as the text in the Toggle Dropdowns for FAQ items 54 | static let listTextColor: Color = .primary 55 | } 56 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Model/SupportColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SupportColor.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 05/12/2021. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | // Used to pass colors from JSON to SwiftUI. You can customize the colors used here to match your project 12 | enum SupportColor: Int, Codable { 13 | case primary = 0 14 | case secondary = 1 15 | case tertiary = 2 16 | 17 | var color: Color { 18 | switch self { 19 | case .primary: 20 | return .workColor 21 | case .secondary: 22 | return .lifeColor 23 | case .tertiary: 24 | return .green 25 | } 26 | } 27 | 28 | init(colorIndex: Int) { 29 | if let color = SupportColor(rawValue: colorIndex) { 30 | self = color 31 | } else { 32 | self = .primary 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Model/SupportItemPage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SupportItemPage.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 01/12/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | /// The model for the individual pages you can link to from a header of highlighted support item 11 | struct SupportItemPage: Identifiable, Codable { 12 | let id: Int 13 | let title: String 14 | let subtitle: String 15 | let videoURL: String? 16 | let darkVideoURL: String? 17 | let imageURL: String? 18 | let darkImageURL: String? 19 | 20 | init( 21 | id: Int, 22 | title: String, 23 | subtitle: String, 24 | videoURL: String? = nil, 25 | darkVideoURL: String? = nil, 26 | imageURL: String? = nil, 27 | darkImageURL: String? = nil 28 | ) { 29 | self.id = id 30 | self.title = title 31 | self.subtitle = subtitle 32 | self.videoURL = videoURL 33 | self.darkVideoURL = darkVideoURL 34 | self.imageURL = imageURL 35 | self.darkImageURL = darkImageURL 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Model/SupportItemable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SupportItemable.swift 3 | // SnoozeSupport 4 | // 5 | // Created by Jordi Bruin on 25/01/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol SupportItemable { 11 | var id: Int { get } 12 | var title: String { get } 13 | } 14 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Model/SupportLocale.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SupportLocale.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 01/12/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Enum that determines the user's device language so that it can fetch different JSON for different localizations 11 | enum SupportLocale: CaseIterable { 12 | case english 13 | // case dutch 14 | 15 | /// Here you can define different urls to use for the different locales you support in your app. Add the langauge to url and languagecode and make sure they match what the system returns in Locale.current.langaugeCode. 16 | var url: URL { 17 | switch self { 18 | case .english: 19 | return URL(string: "https://simplejsoncms.com/api/59cq27p0jp8")! 20 | // case .dutch: 21 | // return URL(string: "https://simplejsoncms.com/api/kpav6tkbns")! 22 | } 23 | } 24 | 25 | var languageCode: String { 26 | switch self { 27 | // case .dutch: 28 | // return "nl" 29 | default: 30 | return "en" 31 | } 32 | } 33 | 34 | init() { 35 | guard let deviceLanguage = Locale.current.languageCode else { 36 | print("no languagecode") 37 | self = .english 38 | return 39 | } 40 | 41 | guard let matchingLanguage = SupportLocale.allCases.first(where: { $0.languageCode == deviceLanguage }) else { 42 | print("no match found") 43 | self = .english 44 | return 45 | } 46 | 47 | print("found a matching langauge \(matchingLanguage)") 48 | self = matchingLanguage 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Model/SupportTypes/ChangelogItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChangelogItem.swift 3 | // SnoozeSupport 4 | // 5 | // Created by Jordi Bruin on 02/01/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ChangelogItem: Identifiable, Codable, Comparable { 11 | static func < (lhs: ChangelogItem, rhs: ChangelogItem) -> Bool { 12 | return lhs.id < rhs.id 13 | } 14 | 15 | static func == (lhs: ChangelogItem, rhs: ChangelogItem) -> Bool { 16 | return lhs.id == rhs.id 17 | } 18 | 19 | let id: Int 20 | let version: String 21 | let date: String? 22 | let notes: [ReleaseNote] 23 | 24 | init(from decoder: Decoder) throws { 25 | let container = try decoder.container(keyedBy: CodingKeys.self) 26 | id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0 27 | version = try container.decodeIfPresent(String.self, forKey: .version) ?? "" 28 | date = try container.decodeIfPresent(String.self, forKey: .date) 29 | notes = try container.decodeIfPresent([ReleaseNote].self, forKey: .notes) ?? [] 30 | } 31 | 32 | init( 33 | id: Int, 34 | version: String, 35 | date: String? = nil, 36 | notes: [ReleaseNote] 37 | ) { 38 | self.id = id 39 | self.version = version 40 | self.date = date 41 | self.notes = notes 42 | } 43 | 44 | static let example = ChangelogItem( 45 | id: 1, 46 | version: "1.2", 47 | date: "2 January 2022", 48 | notes: [ 49 | ReleaseNote(id: 1, releaseNote: "This is the note"), 50 | ] 51 | ) 52 | } 53 | 54 | struct ReleaseNote: Identifiable, Codable { 55 | let id: Int 56 | let releaseNote: String 57 | 58 | init( 59 | id: Int, 60 | releaseNote: String 61 | ) { 62 | self.id = id 63 | self.releaseNote = releaseNote 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Model/SupportTypes/FAQItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FAQItem.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 05/12/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FAQSection: Identifiable, Codable, SupportItemable { 11 | let id: Int 12 | let title: String 13 | let items: [FAQItem] 14 | var hasDetailPage: Bool 15 | var viewAllText: String 16 | 17 | init(from decoder: Decoder) throws { 18 | let container = try decoder.container(keyedBy: CodingKeys.self) 19 | id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0 20 | title = try container.decodeIfPresent(String.self, forKey: .title) ?? "" 21 | items = try container.decodeIfPresent([FAQItem].self, forKey: .items) ?? [] 22 | hasDetailPage = try container.decodeIfPresent(Bool.self, forKey: .hasDetailPage) ?? false 23 | viewAllText = try container.decodeIfPresent(String.self, forKey: .viewAllText) ?? "View all" 24 | } 25 | 26 | init(id: Int, title: String, items: [FAQItem], hasDetailPage: Bool? = nil) { 27 | self.id = id 28 | self.title = title 29 | self.items = items 30 | self.hasDetailPage = hasDetailPage ?? false 31 | viewAllText = "View all" 32 | } 33 | 34 | static let example = FAQSection(id: 0, title: "Section title", items: [ 35 | FAQItem(id: Int.random(in: 1 ... 4_032_304), title: "How to enable subsitles", text: "Four simple steps"), 36 | FAQItem(id: Int.random(in: 1 ... 4_032_304), title: "How to enable subsitles", text: "Four simple steps"), 37 | ]) 38 | } 39 | 40 | struct FAQItem: Identifiable, Codable, SupportItemable { 41 | let id: Int 42 | let title: String 43 | let text: String 44 | 45 | init(from decoder: Decoder) throws { 46 | let container = try decoder.container(keyedBy: CodingKeys.self) 47 | id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0 48 | title = try container.decodeIfPresent(String.self, forKey: .title) ?? "" 49 | text = try container.decodeIfPresent(String.self, forKey: .text) ?? "" 50 | } 51 | 52 | init( 53 | id: Int, 54 | title: String, 55 | text: String 56 | ) { 57 | self.id = id 58 | self.title = title 59 | self.text = text 60 | } 61 | 62 | static let example = FAQItem(id: Int.random(in: 1 ... 4_032_304), title: "How to enable subsitles", text: "Four simple steps") 63 | } 64 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Model/SupportTypes/HeaderItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeaderItem.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 05/12/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | struct HeaderItem: Identifiable, Codable, SupportPageable, SupportItemable { 11 | let id: Int 12 | let title: String 13 | let imageName: String 14 | let color: SupportColor 15 | let supportPages: [SupportItemPage] 16 | 17 | init(from decoder: Decoder) throws { 18 | let container = try decoder.container(keyedBy: CodingKeys.self) 19 | id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0 20 | title = try container.decodeIfPresent(String.self, forKey: .title) ?? "" 21 | imageName = try container.decodeIfPresent(String.self, forKey: .imageName) ?? "" 22 | color = try container.decodeIfPresent(SupportColor.self, forKey: .color) ?? .primary 23 | supportPages = try container.decodeIfPresent([SupportItemPage].self, forKey: .supportPages) ?? [] 24 | } 25 | 26 | init( 27 | id: Int, 28 | title: String, 29 | imageName: String? = "", 30 | color: SupportColor = .primary, 31 | supportPages: [SupportItemPage] 32 | ) { 33 | self.id = id 34 | self.title = title 35 | self.imageName = imageName ?? "" 36 | self.color = color 37 | self.supportPages = supportPages 38 | } 39 | 40 | static let example = HeaderItem(id: Int.random(in: 1 ... 4_032_304), title: "How to enable subsitles", imageName: "captions.bubble.fill", color: .primary, supportPages: []) 41 | } 42 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Model/SupportTypes/HighlightedItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HighlightedItem.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 05/12/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | struct HighlightedItem: Identifiable, Codable, SupportPageable { 11 | let id: Int 12 | let title: String 13 | let subtitle: String 14 | let imageName: String 15 | let emoji: String? 16 | let color: SupportColor? 17 | let supportPages: [SupportItemPage] 18 | 19 | init(from decoder: Decoder) throws { 20 | let container = try decoder.container(keyedBy: CodingKeys.self) 21 | id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0 22 | title = try container.decodeIfPresent(String.self, forKey: .title) ?? "" 23 | subtitle = try container.decodeIfPresent(String.self, forKey: .subtitle) ?? "" 24 | imageName = try container.decodeIfPresent(String.self, forKey: .imageName) ?? "" 25 | emoji = try container.decodeIfPresent(String.self, forKey: .emoji) 26 | color = try container.decodeIfPresent(SupportColor.self, forKey: .color) ?? .primary 27 | supportPages = try container.decodeIfPresent([SupportItemPage].self, forKey: .supportPages) ?? [] 28 | } 29 | 30 | init( 31 | id: Int, 32 | title: String, 33 | subtitle: String, 34 | imageName: String, 35 | emoji: String? = nil, 36 | color: SupportColor? = nil, 37 | supportPages: [SupportItemPage] 38 | ) { 39 | self.id = id 40 | self.title = title 41 | self.subtitle = subtitle 42 | self.imageName = imageName 43 | self.emoji = emoji 44 | self.color = color ?? .primary 45 | self.supportPages = supportPages 46 | } 47 | 48 | static let example = HighlightedItem(id: Int.random(in: 1 ... 4_032_304), title: "How to enable subsitles", subtitle: "Four simple steps", imageName: "captions.bubble.fill", emoji: "😃", color: .primary, supportPages: []) 49 | } 50 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Model/SupportTypes/HighlightedSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HighlightedSection.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 30/12/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | struct HighlightedSection: Identifiable, Codable, SupportItemable { 11 | let id: Int 12 | let title: String 13 | let items: [HighlightedItem] 14 | 15 | init(from decoder: Decoder) throws { 16 | let container = try decoder.container(keyedBy: CodingKeys.self) 17 | id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0 18 | title = try container.decodeIfPresent(String.self, forKey: .title) ?? "" 19 | items = try container.decodeIfPresent([HighlightedItem].self, forKey: .items) ?? [] 20 | } 21 | 22 | init( 23 | id: Int, 24 | title: String, 25 | items: [HighlightedItem] 26 | ) { 27 | self.id = id 28 | self.title = title 29 | self.items = items 30 | } 31 | 32 | static let example = HighlightedSection(id: 0, title: "Section title", items: [ 33 | HighlightedItem(id: Int.random(in: 1 ... 4_032_304), title: "How to enable subsitles", subtitle: "Four simple steps", imageName: "captions.bubble.fill", emoji: "🤫", color: .primary, supportPages: []), 34 | HighlightedItem(id: Int.random(in: 1 ... 4_032_304), title: "How to enable subsitles", subtitle: "Four simple steps", imageName: "captions.bubble.fill", emoji: "🤫", color: .primary, supportPages: []), 35 | ]) 36 | } 37 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Model/SupportTypes/SupportContactItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SupportContactItem.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 05/12/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SupportContactItem: Identifiable, Codable, Equatable { 11 | let id: Int 12 | let title: String 13 | let url: String 14 | let imageName: String 15 | let color: SupportColor 16 | 17 | init(from decoder: Decoder) throws { 18 | let container = try decoder.container(keyedBy: CodingKeys.self) 19 | id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0 20 | title = try container.decodeIfPresent(String.self, forKey: .title) ?? "" 21 | url = try container.decodeIfPresent(String.self, forKey: .url) ?? "" 22 | imageName = try container.decodeIfPresent(String.self, forKey: .imageName) ?? "" 23 | color = try container.decodeIfPresent(SupportColor.self, forKey: .color) ?? .primary 24 | } 25 | 26 | init( 27 | id: Int, 28 | title: String, 29 | url: String, 30 | imageName: String?, 31 | color: SupportColor = .primary 32 | ) { 33 | self.id = id 34 | self.title = title 35 | self.url = url 36 | self.imageName = imageName ?? "" 37 | self.color = color 38 | } 39 | 40 | static let example = SupportContactItem(id: Int.random(in: 1 ... 4_032_304), title: "Naam", url: "https://www.google.com", imageName: "message.fill", color: .primary) 41 | } 42 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Model/SupportTypes/SupportItems.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SupportItems.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 01/12/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | class SupportItems: Codable { 11 | var releaseNotes: SupportReleaseNote? 12 | var headerItems: [HeaderItem]? 13 | var highlightedSections: [HighlightedSection]? 14 | var faqSections: [FAQSection]? 15 | var contactSections: [SupportSection]? 16 | var contactItems: [SupportContactItem]? 17 | var changelogItems: [ChangelogItem]? 18 | 19 | required init(from decoder: Decoder) throws { 20 | let container = try decoder.container(keyedBy: CodingKeys.self) 21 | releaseNotes = try container.decodeIfPresent(SupportReleaseNote.self, forKey: .releaseNotes) 22 | headerItems = try container.decodeIfPresent([HeaderItem].self, forKey: .headerItems) 23 | highlightedSections = try container.decodeIfPresent([HighlightedSection].self, forKey: .highlightedSections) 24 | faqSections = try container.decodeIfPresent([FAQSection].self, forKey: .faqSections) 25 | 26 | contactSections = try container.decodeIfPresent([SupportSection].self, forKey: .contactSections) 27 | contactItems = try container.decodeIfPresent([SupportContactItem].self, forKey: .contactItems) 28 | 29 | changelogItems = try container.decodeIfPresent([ChangelogItem].self, forKey: .changelogItems) 30 | } 31 | } 32 | 33 | protocol SupportPageable { 34 | var id: Int { get } 35 | var supportPages: [SupportItemPage] { get } 36 | } 37 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Model/SupportTypes/SupportReleaseNote.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SupportReleaseNote.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 05/12/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SupportReleaseNote: Identifiable, Codable { 11 | let id: Int 12 | let title: String 13 | let version: String 14 | let description: String 15 | 16 | init(from decoder: Decoder) throws { 17 | let container = try decoder.container(keyedBy: CodingKeys.self) 18 | id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0 19 | title = try container.decodeIfPresent(String.self, forKey: .title) ?? "" 20 | version = try container.decodeIfPresent(String.self, forKey: .version) ?? "" 21 | description = try container.decodeIfPresent(String.self, forKey: .description) ?? "" 22 | } 23 | 24 | init( 25 | id: Int, 26 | title: String, 27 | version: String = "", 28 | description: String = "" 29 | ) { 30 | self.id = id 31 | self.title = title 32 | self.version = version 33 | self.description = description 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Model/SupportTypes/SupportSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SupportSection.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 31/12/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SupportSection: Identifiable, Codable, SupportItemable { 11 | let id: Int 12 | let title: String 13 | let items: [SupportContactItem] 14 | 15 | init(from decoder: Decoder) throws { 16 | let container = try decoder.container(keyedBy: CodingKeys.self) 17 | id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0 18 | title = try container.decodeIfPresent(String.self, forKey: .title) ?? "" 19 | items = try container.decodeIfPresent([SupportContactItem].self, forKey: .items) ?? [] 20 | } 21 | 22 | init( 23 | id: Int, 24 | title: String, 25 | items: [SupportContactItem] 26 | ) { 27 | self.id = id 28 | self.title = title 29 | self.items = items 30 | } 31 | 32 | static let example = SupportSection(id: 0, title: "Contact us", items: [ 33 | SupportContactItem(id: Int.random(in: 1 ... 4_032_304), title: "Naam", url: "https://www.google.com", imageName: "message.fill", color: .primary), 34 | SupportContactItem(id: Int.random(in: 1 ... 4_032_304), title: "Naam", url: "https://www.google.com", imageName: "message.fill", color: .primary), 35 | ]) 36 | } 37 | -------------------------------------------------------------------------------- /DaysSince/Supporter/README.md: -------------------------------------------------------------------------------- 1 | # Supporter - Native In App Support 2 | 3 | Thanks for purchasing Supporter. I hope it will help you give better support to your users in an easy way! I hope Supporter will be your apps' biggest supporter! 😍 4 | 5 | I've been building out an in app support section for my apps. Fully native, but customisable through an external JSON. Since I noticed a lot of interest in a tool like this, I decided to make it available for others as well! 6 | 7 | ## 📝 Features 8 | 9 | - Different display styles for different types of support 10 | - Data fed by external JSON or fallback to local file 11 | - Fully built in SwiftUI 12 | - Different display styles for different types of support 13 | - Lightweight and private analytics to determine the most important questions (Optional) 14 | - Localizable (different support items per locale) 15 | - Supports video FAQs as well as normal support pages 16 | - Contact options 17 | - Display your latest release notes 18 | - Markdown support 19 | - Customize colors 20 | 21 | Coming in 2022 22 | - Mac App for viewing analytics and editing JSON 23 | More customisation (colors and fonts) 24 | 25 | ## 🏗 Setup 26 | 27 | You can add Supporter to your app by copying the files in your project. After that you just present SupportScreen and you're ready to go! 28 | 29 | **Adding Supporter to your project in less than 5 minutes 30 | https://youtu.be/BgEUOO1XZKE 31 | 32 | Supporter supports loading support content from an external JSON, as well as with analytics support through www.countapi.xyz. 33 | 34 | **Hosting your JSON remotely 35 | https://youtu.be/Gs7N8398oxg 36 | 37 | **Setup private analytics in Supporter 38 | https://youtu.be/4NOqKaG6DNY 39 | 40 | 41 | ## 🐣 Early Access 42 | 43 | Supporter is still very much a work in progress but I'm already using it in my own apps right now. You will be able to provide feedback and feature suggestions while it's in early access. 44 | 45 | So please send me an email on jordi@goodsnooze.com or on Twitter @jordibruin if you have any questions or suggestions. 46 | 47 | 48 | ## 👩‍💼 License 49 | 50 | Because you purchased Supporter, you are free to use Supporter in your own (commercial) projects. You can not re-sell or give the code to someone else to use. 51 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Sample JSON/fullSample.json: -------------------------------------------------------------------------------- 1 | { 2 | "contactSections": [ 3 | { 4 | "id": 0, 5 | "title": "Feedback", 6 | "items": [ 7 | { 8 | "id": 0, 9 | "title": "Contact Jordi on Twitter", 10 | "url": "https://www.twitter.com/jordibruin", 11 | "imageName": "message.fill", 12 | "color": 0 13 | }, 14 | { 15 | "id": 1, 16 | "title": "Contact us", 17 | "url": "mailto:jordi@goodsnooze.com,victoria_petrowa@icloud.com?subject=DaysSince%20Support&body=Hi%20there", 18 | "imageName": "message.fill", 19 | "color": 0 20 | } 21 | ] 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Screens/SupportItemDetailView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SupportItemDetailView.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 01/12/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SupportItemDetailView: View { 11 | let item: SupportPageable 12 | @State var activePage: Int = 0 13 | 14 | var body: some View { 15 | tabView() 16 | .navigationBarTitleDisplayMode(.inline) 17 | .tabViewStyle(.page(indexDisplayMode: .always)) 18 | .indexViewStyle(.page(backgroundDisplayMode: .always)) 19 | } 20 | 21 | @ViewBuilder 22 | func tabView() -> some View { 23 | if item.supportPages.isEmpty { 24 | Text("No pages for this support item") 25 | } else { 26 | TabView(selection: $activePage) { 27 | ForEach(item.supportPages) { page in 28 | SupportItemPageView(page: page) 29 | } 30 | } 31 | .background( 32 | Design.backgroundColor 33 | .edgesIgnoringSafeArea(.bottom) 34 | ) 35 | } 36 | } 37 | } 38 | 39 | struct SupportItemView_Previews: PreviewProvider { 40 | static var previews: some View { 41 | SupportItemDetailView(item: HighlightedItem.example) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Utilities/HooliganBundle+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HooliganBundle+Extensions.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 05/12/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Bundle extension to decode local json file 11 | extension Bundle { 12 | func SupporterDecode(_ type: T.Type, from file: String, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> T { 13 | guard let url = url(forResource: file, withExtension: nil) else { 14 | fatalError("Failed to locate \(file) in bundle.") 15 | } 16 | 17 | guard let data = try? Data(contentsOf: url) else { 18 | fatalError("Failed to load \(file) from bundle.") 19 | } 20 | 21 | let decoder = JSONDecoder() 22 | decoder.dateDecodingStrategy = dateDecodingStrategy 23 | decoder.keyDecodingStrategy = keyDecodingStrategy 24 | 25 | do { 26 | return try decoder.decode(T.self, from: data) 27 | } catch let DecodingError.keyNotFound(key, context) { 28 | fatalError("Failed to decode \(file) from bundle due to missing key '\(key.stringValue)' not found – \(context.debugDescription)") 29 | } catch let DecodingError.typeMismatch(_, context) { 30 | fatalError("Failed to decode \(file) from bundle due to type mismatch – \(context.debugDescription)") 31 | } catch let DecodingError.valueNotFound(type, context) { 32 | fatalError("Failed to decode \(file) from bundle due to missing \(type) value – \(context.debugDescription)") 33 | } catch DecodingError.dataCorrupted(_) { 34 | fatalError("Failed to decode \(file) from bundle because it appears to be invalid JSON") 35 | } catch { 36 | fatalError("Failed to decode \(file) from bundle: \(error.localizedDescription)") 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Utilities/HooliganColor+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HooliganColor+Extensions.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 05/12/2021. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension Color { 12 | static var SupporterRandom: Color { 13 | return Color( 14 | red: .random(in: 0 ... 1), 15 | green: .random(in: 0 ... 1), 16 | blue: .random(in: 0 ... 1) 17 | ) 18 | } 19 | 20 | init(SupporterHex: String) { 21 | let hex = SupporterHex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) 22 | var int: UInt64 = 0 23 | Scanner(string: hex).scanHexInt64(&int) 24 | let a, r, g, b: UInt64 25 | switch hex.count { 26 | case 3: // RGB (12-bit) 27 | (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) 28 | case 6: // RGB (24-bit) 29 | (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) 30 | case 8: // ARGB (32-bit) 31 | (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) 32 | default: 33 | (a, r, g, b) = (1, 1, 1, 0) 34 | } 35 | 36 | self.init( 37 | .sRGB, 38 | red: Double(r) / 255, 39 | green: Double(g) / 255, 40 | blue: Double(b) / 255, 41 | opacity: Double(a) / 255 42 | ) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Utilities/HooliganString+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HooliganString+Extensions.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 05/12/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | // Rendering markdown is only supported on iOS 15 11 | 12 | /// For rendering markdown in JSON input 13 | extension String { 14 | @available(iOS 15, *) 15 | func toMarkdown() -> AttributedString { 16 | do { 17 | return try AttributedString(markdown: self) 18 | } catch { 19 | print("Error parsing Markdown for string \(self): \(error)") 20 | return AttributedString(self) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Video/HooliganVideoPlayerController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HooliganVideoPlayerController.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 05/12/2021. 6 | // 7 | 8 | import AVKit 9 | import Foundation 10 | import SwiftUI 11 | import UIKit 12 | 13 | struct SupporterVideoPlayerController: UIViewControllerRepresentable { 14 | var videoURL: URL 15 | @Binding var stopPlayer: Bool 16 | @Binding var muted: Bool 17 | 18 | typealias UIViewControllerType = AVPlayerViewController 19 | 20 | func makeUIViewController(context _: Context) -> AVPlayerViewController { 21 | let player = AVPlayer(url: videoURL) 22 | 23 | let playerViewController = AVPlayerViewController() 24 | 25 | playerViewController.player = player 26 | playerViewController.showsPlaybackControls = false 27 | playerViewController.videoGravity = .resizeAspect 28 | playerViewController.view.backgroundColor = .clear 29 | player.isMuted = true 30 | 31 | player.play() 32 | 33 | // Rewind video at the end 34 | 35 | // This causes memory leak 36 | // NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, 37 | // object: nil, 38 | // queue: nil) { [weak self] note in 39 | // player.seek(to: CMTime.zero) 40 | // player.play() 41 | // } 42 | // 43 | return playerViewController 44 | } 45 | 46 | func updateUIViewController(_ uiViewController: AVPlayerViewController, context _: Context) { 47 | if muted { 48 | uiViewController.player?.isMuted = true 49 | } else { 50 | uiViewController.player?.isMuted = false 51 | } 52 | 53 | if stopPlayer { 54 | uiViewController.player?.pause() 55 | } else { 56 | uiViewController.player?.play() 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Views/Contact/ContactItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContactItemView.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 04/12/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContactItemView: View { 11 | @Environment(\.openURL) var openURL 12 | 13 | let item: SupportContactItem 14 | 15 | var body: some View { 16 | Button { 17 | if let url = URL(string: item.url) { 18 | openURL(url) 19 | } else { 20 | print("Not a valid url") 21 | } 22 | } label: { 23 | HStack { 24 | item.color.color 25 | .frame(width: 30, height: 30) 26 | .cornerRadius(8) 27 | .overlay( 28 | Image(systemName: item.imageName) 29 | .foregroundColor(.white) 30 | .font(.title3) 31 | ) 32 | .padding(.leading, -10) 33 | 34 | Text(item.title) 35 | .font(.system(.body, design: .rounded)) 36 | .bold() 37 | .foregroundColor(Design.listTitleColor) 38 | 39 | Spacer() 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Views/Contact/SupportContactSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SupportContactSection.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 30/12/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SupportContactSection: View { 11 | @EnvironmentObject var support: SupportFetcher 12 | 13 | var body: some View { 14 | if !support.contactSections.isEmpty { 15 | suppportSections 16 | } else { 17 | singleSection 18 | } 19 | } 20 | 21 | var suppportSections: some View { 22 | ForEach(support.contactSections) { section in 23 | Section { 24 | ForEach(section.items) { item in 25 | ContactItemView(item: item) 26 | // .listRowBackground(Design.listRowBackgroundColor) 27 | } 28 | } header: { 29 | if !section.title.isEmpty { 30 | Text(section.title) 31 | } else {} 32 | } 33 | } 34 | } 35 | 36 | var singleSection: some View { 37 | Section { 38 | ForEach(support.contactItems) { item in 39 | ContactItemView(item: item) 40 | .listRowBackground(Design.listRowBackgroundColor) 41 | } 42 | } 43 | } 44 | } 45 | 46 | struct SupportContactSection_Previews: PreviewProvider { 47 | static var previews: some View { 48 | SupportContactSection() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Views/FAQ/FAQDetailView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FAQDetailView.swift 3 | // SnoozeSupport 4 | // 5 | // Created by Jordi Bruin on 03/01/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct FAQDetailView: View { 11 | let section: FAQSection 12 | 13 | var body: some View { 14 | List { 15 | ForEach(section.items) { item in 16 | FAQItemView(item: item) 17 | } 18 | } 19 | .navigationTitle(section.title) 20 | } 21 | } 22 | 23 | struct FAQDetailView_Previews: PreviewProvider { 24 | static var previews: some View { 25 | FAQDetailView( 26 | section: FAQSection( 27 | id: 1, 28 | title: "", 29 | items: [ 30 | FAQItem(id: 1, title: "FAQ item Title", text: "FAQ Item text"), 31 | ] 32 | ) 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Views/FAQ/FAQItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FAQItemView.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 01/12/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct FAQItemView: View { 11 | let item: FAQItem 12 | 13 | var body: some View { 14 | DisclosureGroup { 15 | Text(item.text.toMarkdown()) 16 | .foregroundColor(Design.listTextColor) 17 | .font(.system(.body, design: .rounded)) 18 | .offset(x: -20) 19 | .padding(.vertical, 12) 20 | .fixedSize(horizontal: false, vertical: true) 21 | 22 | } label: { 23 | Text(item.title) 24 | .font(.system(.body, design: .rounded)) 25 | .bold() 26 | .foregroundColor(Design.listTitleColor) 27 | .padding(.vertical, 12) 28 | .fixedSize(horizontal: false, vertical: true) 29 | } 30 | } 31 | } 32 | 33 | struct FAQItemView_Previews: PreviewProvider { 34 | static var previews: some View { 35 | FAQItemView(item: FAQItem.example) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Views/FAQ/SupportFAQSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SupportFAQSection.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 30/12/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SupportFAQSection: View { 11 | @EnvironmentObject var support: SupportFetcher 12 | 13 | var body: some View { 14 | ForEach(support.faqSections) { section in 15 | Section { 16 | ForEach(section.hasDetailPage ? Array(section.items.prefix(3)) : section.items) { item in 17 | FAQItemView(item: item) 18 | .listRowBackground(Design.listRowBackgroundColor) 19 | } 20 | 21 | if section.hasDetailPage { 22 | NavigationLink { 23 | FAQDetailView(section: section) 24 | } label: { 25 | Text(section.viewAllText) 26 | .font(.system(.body, design: .rounded)) 27 | .bold() 28 | .foregroundColor(Design.listTitleColor) 29 | .padding(.vertical, 12) 30 | } 31 | .listRowBackground(Design.listRowBackgroundColor) 32 | } 33 | } header: { 34 | if !section.title.isEmpty { 35 | Text(section.title) 36 | } else {} 37 | } 38 | } 39 | } 40 | } 41 | 42 | struct SupportFAQSection_Previews: PreviewProvider { 43 | static var previews: some View { 44 | SupportFAQSection() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Views/Header/SupportHeaderSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SupportHeaderSection.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 30/12/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SupportHeaderSection: View { 11 | @EnvironmentObject var support: SupportFetcher 12 | 13 | var body: some View { 14 | Section { 15 | TabView { 16 | ForEach(support.headerItems) { item in 17 | NavigationLink { 18 | SupportItemDetailView(item: item) 19 | } label: { 20 | SupportHeaderView(item: item) 21 | } 22 | .buttonStyle(FlatLinkStyle()) 23 | } 24 | } 25 | .tabViewStyle(.page) 26 | .frame(height: 180) 27 | .listRowInsets( 28 | EdgeInsets( 29 | top: 0, 30 | leading: 0, 31 | bottom: 0, 32 | trailing: 0 33 | ) 34 | ) 35 | } 36 | .listRowBackground(Color(.systemBackground)) 37 | } 38 | } 39 | 40 | struct SupportHeaderSection_Previews: PreviewProvider { 41 | static var previews: some View { 42 | SupportHeaderSection() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Views/Header/SupportHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SupportHeaderView.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 29/11/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SupportHeaderView: View { 11 | let item: HeaderItem 12 | 13 | var body: some View { 14 | ZStack(alignment: .topLeading) { 15 | item.color.color 16 | .overlay( 17 | HStack { 18 | Spacer() 19 | VStack { 20 | Spacer() 21 | if #available(iOS 15.0, *) { 22 | Image(systemName: item.imageName) 23 | .symbolRenderingMode(.hierarchical) 24 | .font(.system(size: 50)) 25 | .foregroundColor(.white) 26 | } else { 27 | Image(systemName: item.imageName) 28 | .font(.system(size: 50)) 29 | .foregroundColor(.white) 30 | } 31 | } 32 | } 33 | .padding(12) 34 | ) 35 | Text(item.title) 36 | .font(.system(.largeTitle, design: .rounded)) 37 | .bold() 38 | .padding() 39 | .foregroundColor(.white) 40 | .padding(.trailing, 40) 41 | .multilineTextAlignment(.leading) 42 | } 43 | // } 44 | .buttonStyle(FlatLinkStyle()) 45 | } 46 | } 47 | 48 | struct SupportHeaderView_Previews: PreviewProvider { 49 | static var previews: some View { 50 | SupportHeaderView(item: HeaderItem.example) 51 | } 52 | } 53 | 54 | struct FlatLinkStyle: ButtonStyle { 55 | func makeBody(configuration: Configuration) -> some View { 56 | configuration.label 57 | } 58 | } 59 | 60 | struct SupportDetailScreen: View { 61 | let title: String 62 | let description: String 63 | 64 | var body: some View { 65 | ScrollView { 66 | VStack(alignment: .leading, spacing: 16) { 67 | Text(title) 68 | .font(.system(.title, design: .rounded)) 69 | .bold() 70 | 71 | Text(description) 72 | .font(.system(.body, design: .rounded)) 73 | } 74 | .multilineTextAlignment(.leading) 75 | .padding() 76 | } 77 | .background(Color(.systemBackground)) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Views/Highlighted/HighlightedItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HighlightedItemView.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 01/12/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct HighlightedItemView: View { 11 | let item: HighlightedItem 12 | @Environment(\.sizeCategory) var sizeCategory 13 | 14 | var body: some View { 15 | if !sizeCategory.isAccessibilityCategory { 16 | regularView 17 | } else { 18 | accessibleView 19 | } 20 | } 21 | 22 | var emoji: some View { 23 | Text(item.emoji ?? "") 24 | .font(.title2) 25 | .padding(.leading, -8) 26 | } 27 | 28 | var icon: some View { 29 | Image(systemName: item.imageName) 30 | .font(.title) 31 | .foregroundColor(item.color?.color) 32 | .padding(.leading, -8) 33 | } 34 | 35 | var text: some View { 36 | VStack(alignment: .leading) { 37 | Text(item.title) 38 | .foregroundColor(Design.listTitleColor) 39 | .bold() 40 | if !item.subtitle.isEmpty { 41 | Text(item.subtitle) 42 | .foregroundColor(Design.listTextColor) 43 | .font(.caption) 44 | .opacity(0.8) 45 | } 46 | } 47 | .padding(.vertical, 12) 48 | .padding(.trailing, 4) 49 | } 50 | 51 | var regularView: some View { 52 | HStack { 53 | if item.emoji != nil { 54 | emoji 55 | } else { 56 | icon 57 | } 58 | text 59 | } 60 | } 61 | 62 | var accessibleView: some View { 63 | VStack(alignment: .leading, spacing: 0) { 64 | if item.emoji != nil { 65 | emoji 66 | } else { 67 | icon 68 | } 69 | text 70 | } 71 | } 72 | } 73 | 74 | struct HighlightedItemView_Previews: PreviewProvider { 75 | static var previews: some View { 76 | HighlightedItemView(item: HighlightedItem.example) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Views/Highlighted/SupportHighlightedSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SupportHighlightedSection.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 30/12/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SupportHighlightedSection: View { 11 | @EnvironmentObject var support: SupportFetcher 12 | 13 | var body: some View { 14 | ForEach(support.highlightedSections) { section in 15 | Section { 16 | ForEach(section.items) { item in 17 | NavigationLink { 18 | SupportItemDetailView(item: item) 19 | } label: { 20 | HighlightedItemView(item: item) 21 | } 22 | .listRowBackground(Design.listRowBackgroundColor) 23 | } 24 | } header: { 25 | if !section.title.isEmpty { 26 | Text(section.title) 27 | } else {} 28 | } 29 | } 30 | } 31 | } 32 | 33 | struct SupportHighlightedSection_Preview: PreviewProvider { 34 | static var previews: some View { 35 | SupportHighlightedSection() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Views/ReleaseNotes/ChangelogUpdateCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChangelogUpdateCell.swift 3 | // SnoozeSupport 4 | // 5 | // Created by Jordi Bruin on 02/01/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ChangelogUpdateCell: View { 11 | let item: ChangelogItem 12 | 13 | var body: some View { 14 | Section { 15 | VStack(alignment: .leading, spacing: 12) { 16 | ForEach(item.notes) { note in 17 | Text("• \(note.releaseNote)") 18 | } 19 | } 20 | .padding(.vertical, 12) 21 | } header: { 22 | HStack { 23 | Text(item.version) 24 | .bold() 25 | .font(.system(.title3, design: .rounded)) 26 | .foregroundColor(.primary) 27 | .textCase(nil) 28 | .padding(.leading, -8) 29 | } 30 | } 31 | } 32 | } 33 | 34 | struct ChangelogUpdateCell_Previews: PreviewProvider { 35 | static var previews: some View { 36 | ChangelogUpdateCell(item: ChangelogItem(id: 1, version: "test", notes: [ 37 | ReleaseNote(id: 1, releaseNote: "Note"), 38 | ReleaseNote(id: 2, releaseNote: "Notesds"), 39 | ReleaseNote(id: 3, releaseNote: "Note"), 40 | ])) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Views/ReleaseNotes/ChangelogView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChangelogView.swift 3 | // SnoozeSupport 4 | // 5 | // Created by Jordi Bruin on 02/01/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ChangelogView: View { 11 | @Environment(\.presentationMode) var presentationMode 12 | let items: [ChangelogItem] 13 | 14 | var body: some View { 15 | NavigationView { 16 | List { 17 | ForEach(items.reversed()) { item in 18 | ChangelogUpdateCell(item: item) 19 | } 20 | } 21 | .navigationBarTitleDisplayMode(.inline) 22 | .toolbar { 23 | ToolbarItem(placement: .navigationBarTrailing) { 24 | Button { 25 | presentationMode.wrappedValue.dismiss() 26 | } label: { 27 | Image(systemName: "xmark.circle.fill") 28 | .foregroundColor(.secondary) 29 | .font(.title2) 30 | } 31 | } 32 | } 33 | .onAppear { 34 | guard let newestUpdate = items.map({ item in 35 | item.id 36 | }).max() else { return } 37 | 38 | // No longer show it on the main support page after a user tapped the new update icon 39 | UserDefaults.standard.set(newestUpdate, forKey: "latestVersionIdOpened") 40 | } 41 | } 42 | } 43 | } 44 | 45 | struct ChangelogView_Previews: PreviewProvider { 46 | static var previews: some View { 47 | ChangelogView(items: []) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /DaysSince/Supporter/Views/ReleaseNotes/NewUpdateBanner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NewUpdateBanner.swift 3 | // Supporter 4 | // 5 | // Created by Jordi Bruin on 05/12/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct NewUpdateBanner: View { 11 | let item: ChangelogItem 12 | 13 | var body: some View { 14 | ZStack(alignment: .topLeading) { 15 | Color(.systemBackground) 16 | .overlay( 17 | VStack { 18 | HStack { 19 | Spacer() 20 | Button {} label: { 21 | Image(systemName: "xmark.circle.fill") 22 | .font(.title2) 23 | // .symbolRenderingMode(.hierarchical) iOS 15 24 | .foregroundColor(.blue) 25 | } 26 | } 27 | .padding() 28 | 29 | Spacer() 30 | } 31 | ) 32 | textContent 33 | } 34 | // Make the content fill the entire space of the cell 35 | .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) 36 | // Prevent taps from lighting up the cell 37 | .buttonStyle(FlatLinkStyle()) 38 | } 39 | 40 | var titleAndVersion: some View { 41 | VStack(alignment: .leading, spacing: 4) { 42 | Text(item.version) 43 | .font(.system(.title2, design: .rounded)) 44 | .bold() 45 | } 46 | .padding(.trailing, 40) 47 | } 48 | 49 | var textContent: some View { 50 | VStack(alignment: .leading, spacing: 12) { 51 | titleAndVersion 52 | 53 | VStack(alignment: .leading, spacing: 8) { 54 | ForEach(item.notes.prefix(2)) { note in 55 | Text("• \(note.releaseNote)") 56 | } 57 | 58 | if item.notes.count > 2 { 59 | Text("...") 60 | } 61 | } 62 | } 63 | .foregroundColor(.primary) 64 | .multilineTextAlignment(.leading) 65 | .padding() 66 | } 67 | } 68 | 69 | struct NewUpdateBanner_Previews: PreviewProvider { 70 | static var previews: some View { 71 | NewUpdateBanner(item: ChangelogItem(id: 1, version: "1.2", date: "January", notes: [ 72 | ReleaseNote(id: 1, releaseNote: "Updated something"), 73 | ReleaseNote(id: 2, releaseNote: "Fixed something else"), 74 | ReleaseNote(id: 3, releaseNote: "Bugfixes"), 75 | ])) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Widget/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Widget/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Widget/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Widget/Assets.xcassets/WidgetBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Widget/Families Single Event Widget/SingleEventWidget_Circular.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SingleEventWidget_Circular.swift 3 | // WidgetExtension 4 | // 5 | // Created by Victoria Petrova on 07/04/2025. 6 | // 7 | 8 | import SwiftUI 9 | import Foundation 10 | import WidgetKit 11 | 12 | struct SingleEventWidget_Circular: View { 13 | var event: WidgetContent 14 | 15 | var body: some View { 16 | VStack { 17 | if event.name == "No event" { 18 | Text("Tap to select!") 19 | } else { 20 | Text("\(event.daysNumber)") 21 | .font(.system(.title, design: .rounded)) 22 | .bold() 23 | .widgetAccentable() 24 | Text(event.daysNumber > 0 ? "days" : "day") 25 | .fontDesign(.rounded) 26 | } 27 | } 28 | .widgetBackground(Color.clear) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Widget/Families Single Event Widget/SingleEventWidget_Inline.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SingleEventWidget_Inline.swift 3 | // WidgetExtension 4 | // 5 | // Created by Victoria Petrova on 07/04/2025. 6 | // 7 | 8 | import SwiftUI 9 | import Foundation 10 | import WidgetKit 11 | 12 | struct SingleEventWidget_Inline: View { 13 | var event: WidgetContent 14 | 15 | var body: some View { 16 | Group { 17 | if event.name == "No event" { 18 | Text("Tap to select event!") 19 | } else { 20 | Text("\(event.daysNumber) \(event.daysNumber > 0 ? "days" : "day") since \(event.name) ") 21 | .privacySensitive() 22 | } 23 | } 24 | .widgetBackground(Color.clear) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Widget/Families Single Event Widget/SingleEventWidget_Rectangular.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SingleEventWidget_Rectangular.swift 3 | // WidgetExtension 4 | // 5 | // Created by Victoria Petrova on 07/04/2025. 6 | // 7 | 8 | import SwiftUI 9 | import Foundation 10 | import WidgetKit 11 | 12 | struct SingleEventWidget_Rectangular: View { 13 | var event: WidgetContent 14 | 15 | var body: some View { 16 | HStack { 17 | if event.name == "No event" { 18 | Text("Tap to select event!") 19 | } else { 20 | Text(event.name) 21 | .font(.system(.headline, design: .rounded)) 22 | .privacySensitive() 23 | Spacer() 24 | VStack { 25 | Text("\(event.daysNumber)") 26 | .fontDesign(.rounded) 27 | .bold() 28 | .widgetAccentable() 29 | Text(event.daysNumber > 0 ? "days" : "day") 30 | .fontDesign(.rounded) 31 | } 32 | } 33 | } 34 | .widgetBackground(Color.clear) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Widget/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionAttributes 8 | 9 | IntentsRestrictedWhileLocked 10 | 11 | IntentsRestrictedWhileProtectedDataUnavailable 12 | 13 | IntentsSupported 14 | 15 | SelectMultipleEventsIntent 16 | SelectEventIntent 17 | 18 | 19 | NSExtensionPointIdentifier 20 | com.apple.widgetkit-extension 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Widget/Provider/MultiEventProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MultiEventProvider.swift 3 | // WidgetExtension 4 | // 5 | // Created by Victoria Petrova on 07/04/2025. 6 | // 7 | 8 | import Foundation 9 | import WidgetKit 10 | import SwiftUI 11 | 12 | // Define the provider for the multi-event widget 13 | struct MultipleEventsProvider: IntentTimelineProvider { 14 | @AppStorage("items", store: UserDefaults(suiteName: "group.goodsnooze.dayssince")) var items: [DSItem] = [] 15 | 16 | // Placeholder for context 17 | func placeholder(in context: Context) -> MultipleEventsEntry { 18 | // Adjust placeholder count based on family if needed 19 | MultipleEventsEntry.placeholder() 20 | } 21 | 22 | // Snapshot for widget gallery 23 | func getSnapshot(for configuration: SelectMultipleEventsIntent, in context: Context, completion: @escaping (MultipleEventsEntry) -> Void) { 24 | // Provide realistic snapshot data up to 5 25 | completion(MultipleEventsEntry.snapshot()) 26 | } 27 | 28 | // Timeline generation 29 | func getTimeline(for configuration: SelectMultipleEventsIntent, in context: Context, completion: @escaping (Timeline) -> Void) { 30 | var selectedEvents: [WidgetContent] = [] 31 | 32 | // Fetch events based on intent configuration (now including event4 and event5) 33 | let eventIDs = [ 34 | configuration.event1?.identifier, 35 | configuration.event2?.identifier, 36 | configuration.event3?.identifier, 37 | configuration.event4?.identifier, 38 | configuration.event5?.identifier 39 | ].compactMap { $0 } // Get non-nil IDs 40 | 41 | for eventId in eventIDs { 42 | if let matchingItem = items.first(where: { $0.id.uuidString == eventId }) { 43 | selectedEvents.append(WidgetContent(item: matchingItem)) 44 | } 45 | } 46 | 47 | 48 | // The view will display however many were actually selected (up to 5). 49 | // Create the timeline entry with the fetched events 50 | var entry = MultipleEventsEntry(date: Date(), events: [WidgetContent(date: Date(), name: "No events", id: UUID(), color: .green, daysNumber: 4)]) 51 | 52 | if !selectedEvents.isEmpty { 53 | entry = MultipleEventsEntry(date: Date(), events: selectedEvents) 54 | } 55 | 56 | let timeline = Timeline(entries: [entry], policy: .atEnd) 57 | completion(timeline) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Widget/Provider/SingleEventProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SingleEventProvider.swift 3 | // WidgetExtension 4 | // 5 | // Created by Victoria Petrova on 07/04/2025. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import WidgetKit 11 | 12 | struct Provider: IntentTimelineProvider { 13 | @AppStorage("items", store: UserDefaults(suiteName: "group.goodsnooze.dayssince")) var items: [DSItem] = [] 14 | 15 | func placeholder(in _: Context) -> WidgetContent { 16 | let content = WidgetContent(date: Date(), name: "Adopted Leo 🐱", id: UUID(), color: Color.lifeColor, daysNumber: 237) 17 | 18 | return content 19 | } 20 | 21 | public func getSnapshot( 22 | for _: SelectEventIntent, 23 | in _: Context, 24 | completion: @escaping (WidgetContent) -> Void 25 | ) { 26 | let content = WidgetContent(date: Date(), name: "Adopted Charlie 🐶", id: UUID(), color: .green, daysNumber: 45) 27 | completion(content) 28 | } 29 | 30 | public func getTimeline( 31 | for configuration: SelectEventIntent, 32 | in _: Context, 33 | completion: @escaping (Timeline) -> Void 34 | ) { 35 | let eventId = configuration.event?.identifier ?? "" 36 | 37 | if let matchingEvent = items.first(where: { $0.id.uuidString == eventId }) { 38 | let content = WidgetContent(item: matchingEvent) 39 | completion(Timeline(entries: [content], policy: .atEnd)) 40 | } else { 41 | let content = WidgetContent(date: Date(), name: "No event", id: UUID(), color: .green, daysNumber: 4) 42 | completion(Timeline(entries: [content], policy: .atEnd)) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Widget/SingleEventWidgetView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SingleEventWidgetView.swift 3 | // WidgetExtension 4 | // 5 | // Created by Victoria Petrova on 07/04/2025. 6 | // 7 | 8 | import SwiftUI 9 | import WidgetKit 10 | 11 | struct EventCardWidgetView: View { 12 | let event: WidgetContent 13 | 14 | @Environment(\.widgetFamily) var family 15 | 16 | @ViewBuilder 17 | var body: some View { 18 | switch family { 19 | case .accessoryCircular: 20 | SingleEventWidget_Circular(event: event) 21 | case .accessoryInline: 22 | SingleEventWidget_Inline(event: event) 23 | case .accessoryRectangular: 24 | SingleEventWidget_Rectangular(event: event) 25 | case .systemSmall, .systemMedium: 26 | SingleEventWidget_Standard(event: event) 27 | default: 28 | Text("Some other WidgetFamily in the future.") 29 | } 30 | } 31 | } 32 | 33 | struct EventCardWidgetView_Previews: PreviewProvider { 34 | static var previews: some View { 35 | Group { 36 | EventCardWidgetView(event: WidgetContent(date: Date.now, name: "Test event", id: UUID(), color: .workColor, daysNumber: 7)) 37 | .previewContext(WidgetPreviewContext(family: .systemSmall)) 38 | .previewDisplayName("Small Widget") 39 | 40 | EventCardWidgetView(event: WidgetContent(date: Date.now, name: "Test event", id: UUID(), color: .workColor, daysNumber: 7)) 41 | .previewContext(WidgetPreviewContext(family: .systemMedium)) 42 | .previewDisplayName("Medium Widget") 43 | .environment(\.colorScheme, .dark) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Widget/WidgetContent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WidgetContent.swift 3 | // WidgetExtension 4 | // 5 | // Created by Victoria Petrova on 07/04/2025. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import WidgetKit 11 | 12 | // Define the data structure for the single and multiple event widgets 13 | struct WidgetContent: TimelineEntry, Identifiable { 14 | var date: Date 15 | let name: String 16 | let id: UUID 17 | 18 | let color: Color 19 | let daysNumber: Int 20 | 21 | init(date: Date, name: String, id: UUID, color: Color, daysNumber: Int) { 22 | self.date = date 23 | self.name = name 24 | self.id = id 25 | self.color = color 26 | self.daysNumber = daysNumber 27 | } 28 | 29 | init(item: DSItem) { 30 | date = item.dateLastDone 31 | name = item.name 32 | id = item.id 33 | color = item.category.color.color 34 | 35 | let daysSince = Calendar.current.numberOfDaysBetween(item.dateLastDone, and: Date.now) 36 | daysNumber = abs(daysSince) 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Widget/en.lproj/Widget.strings: -------------------------------------------------------------------------------- 1 | "9AcsmU" = "Select Event"; 2 | 3 | "GyYdqZ" = "Event"; 4 | 5 | "NDZWiK" = "id"; 6 | 7 | "gpCwrM" = "Select Event"; 8 | 9 | "vRNGv4" = "Days Since Event"; 10 | 11 | -------------------------------------------------------------------------------- /WidgetExtension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.goodsnooze.dayssince 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /WidgetIntents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionAttributes 8 | 9 | IntentsRestrictedWhileLocked 10 | 11 | IntentsRestrictedWhileProtectedDataUnavailable 12 | 13 | IntentsSupported 14 | 15 | SelectMultipleEventsIntent 16 | SelectEventIntent 17 | 18 | 19 | NSExtensionPointIdentifier 20 | com.apple.intents-service 21 | NSExtensionPrincipalClass 22 | $(PRODUCT_MODULE_NAME).IntentHandler 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /WidgetIntents/IntentHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntentHandler.swift 3 | // WidgetIntents 4 | // 5 | // Created by Jordi Bruin on 27/06/2022. 6 | // 7 | 8 | import Defaults 9 | import Intents 10 | import SwiftUI 11 | 12 | class IntentHandler: INExtension, SelectEventIntentHandling, SelectMultipleEventsIntentHandling { 13 | @AppStorage("items", store: UserDefaults(suiteName: "group.goodsnooze.dayssince")) var items: [DSItem] = [] 14 | 15 | // MARK: - SelectEventIntentHandling (Single Event) 16 | func provideEventOptionsCollection(for _: SelectEventIntent) async throws -> INObjectCollection { 17 | let events = items.map { WidgetDaysSinceEvent(identifier: $0.id.uuidString, display: $0.name) } 18 | return INObjectCollection(items: events) 19 | } 20 | 21 | // MARK: - SelectMultipleEventsIntentHandling (Multiple Events) 22 | 23 | // Provide options for the first event selection 24 | func provideEvent1OptionsCollection(for intent: SelectMultipleEventsIntent) async throws -> INObjectCollection { 25 | let events = items.map { WidgetDaysSinceEvent(identifier: $0.id.uuidString, display: $0.name) } 26 | return INObjectCollection(items: events) 27 | } 28 | 29 | // Provide options for the second event selection 30 | func provideEvent2OptionsCollection(for intent: SelectMultipleEventsIntent) async throws -> INObjectCollection { 31 | let events = items.map { WidgetDaysSinceEvent(identifier: $0.id.uuidString, display: $0.name) } 32 | return INObjectCollection(items: events) 33 | } 34 | 35 | // Provide options for the third event selection 36 | func provideEvent3OptionsCollection(for intent: SelectMultipleEventsIntent) async throws -> INObjectCollection { 37 | let events = items.map { WidgetDaysSinceEvent(identifier: $0.id.uuidString, display: $0.name) } 38 | return INObjectCollection(items: events) 39 | } 40 | 41 | // Provide options for the fourth event selection 42 | func provideEvent4OptionsCollection(for intent: SelectMultipleEventsIntent) async throws -> INObjectCollection { 43 | let events = items.map { WidgetDaysSinceEvent(identifier: $0.id.uuidString, display: $0.name) } 44 | return INObjectCollection(items: events) 45 | } 46 | 47 | // Provide options for the fifth event selection 48 | func provideEvent5OptionsCollection(for intent: SelectMultipleEventsIntent) async throws -> INObjectCollection { 49 | let events = items.map { WidgetDaysSinceEvent(identifier: $0.id.uuidString, display: $0.name) } 50 | return INObjectCollection(items: events) 51 | } 52 | 53 | override func handler(for intent: INIntent) -> Any { 54 | // This is the default implementation. If you want different objects to handle different intents, 55 | // you can override this and return the handler you want for that particular intent. 56 | return self 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /WidgetIntents/WidgetIntents.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.goodsnooze.dayssince 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------