├── .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 |
--------------------------------------------------------------------------------