From 8716d40a63c945bbc02b1f9321ab438bf09bea53 Mon Sep 17 00:00:00 2001 From: Kevin Li Date: Tue, 14 Jul 2020 22:18:03 -0700 Subject: [PATCH] Bugfix/theme change (#3) * edited the configuration to only include properties that would cause a major reset to the calendar when changed. move properties like theme color and haptics that don't require a hard reset out of the configuration. finalized code regarding haptics. theme color needs to be worked on * added new calendar theme model. everything works babyyyyyy * fixed programmatic theme changing for the monthly calendar view * fixed programmatic theme changing for the yearly calendar view * wip programmatic app theme. not working when nested * fixed the theme change for all views * pointed elegantpages back to latest release * reorganization * added more themes and made the default theme royal blue * added new dark demo gif * added screenshots for new themes * made calendartheme public * Update README.md * made public --- .../brilliantViolet.colorset/Contents.json | 20 +++++ .../craftBrown.colorset/Contents.json | 20 +++++ .../fluorescentPink.colorset/Contents.json | 20 +++++ .../kiwiGreen.colorset/Contents.json | 20 +++++ .../mauvePurple.colorset/Contents.json | 20 +++++ .../orangeYellow.colorset/Contents.json | 20 +++++ .../red.colorset/Contents.json | 20 +++++ .../royalBlue.colorset/Contents.json | 20 +++++ Example/Example.xcodeproj/project.pbxproj | 28 +++++-- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../ExampleCalendarView.swift | 19 ++++- .../Calendar with Accessory View/Visit.swift | 24 +++++- .../ExampleMonthlyCalendarView.swift | 19 ++++- .../ExampleYearlyCalendarView.swift | 19 ++++- .../ExampleSelectionView.swift | 3 +- .../Example/Shared/ChangeThemeButton.swift | 26 ++++++ .../Color+Custom.swift} | 2 +- .../Date+FullDate.swift | 0 Package.resolved | 4 +- Package.swift | 2 +- README.md | 74 +++++++++++++--- Screenshots/brilliantViolet.PNG | Bin 0 -> 227995 bytes Screenshots/craftBrown.PNG | Bin 0 -> 206458 bytes Screenshots/fluorescentPink.PNG | Bin 0 -> 229605 bytes Screenshots/kiwiGreen.PNG | Bin 0 -> 228845 bytes Screenshots/mauvePurple.PNG | Bin 0 -> 227259 bytes Screenshots/orangeYellow.PNG | Bin 0 -> 207088 bytes Screenshots/red.PNG | Bin 0 -> 227410 bytes Screenshots/royalblue.PNG | Bin 0 -> 229115 bytes .../EnvironmentKey+CalendarTheme.swift | 62 ++++++++++++++ .../Models/CalendarConfiguration.swift | 18 +--- .../MonthlyCalendarManager.swift | 41 +++++++-- .../Models/Protocols/Calendar+Buildable.swift | 72 ++++++++++++++++ .../Views/ElegantCalendarView.swift | 4 + .../Views/Monthly/DayView.swift | 12 ++- .../Views/Monthly/MonthView.swift | 8 +- .../Views/Monthly/MonthlyCalendarView.swift | 8 +- .../Views/Yearly/SmallMonthView.swift | 4 +- .../Views/Yearly/YearView.swift | 4 +- .../Views/Yearly/YearlyCalendarView.swift | 79 ++++++++++++++---- 40 files changed, 602 insertions(+), 94 deletions(-) create mode 100644 ElegantCalendar.xcassets/brilliantViolet.colorset/Contents.json create mode 100644 ElegantCalendar.xcassets/craftBrown.colorset/Contents.json create mode 100644 ElegantCalendar.xcassets/fluorescentPink.colorset/Contents.json create mode 100644 ElegantCalendar.xcassets/kiwiGreen.colorset/Contents.json create mode 100644 ElegantCalendar.xcassets/mauvePurple.colorset/Contents.json create mode 100644 ElegantCalendar.xcassets/orangeYellow.colorset/Contents.json create mode 100644 ElegantCalendar.xcassets/red.colorset/Contents.json create mode 100644 ElegantCalendar.xcassets/royalBlue.colorset/Contents.json create mode 100644 Example/Example/Shared/ChangeThemeButton.swift rename Example/Example/{Extensions/Color+BlackPearl.swift => Shared/Color+Custom.swift} (73%) rename Example/Example/{Extensions => Shared}/Date+FullDate.swift (100%) create mode 100644 Screenshots/brilliantViolet.PNG create mode 100644 Screenshots/craftBrown.PNG create mode 100644 Screenshots/fluorescentPink.PNG create mode 100644 Screenshots/kiwiGreen.PNG create mode 100644 Screenshots/mauvePurple.PNG create mode 100644 Screenshots/orangeYellow.PNG create mode 100644 Screenshots/red.PNG create mode 100644 Screenshots/royalblue.PNG create mode 100644 Sources/ElegantCalendar/Helpers/Extensions/EnvironmentKey+CalendarTheme.swift create mode 100644 Sources/ElegantCalendar/Helpers/Models/Protocols/Calendar+Buildable.swift diff --git a/ElegantCalendar.xcassets/brilliantViolet.colorset/Contents.json b/ElegantCalendar.xcassets/brilliantViolet.colorset/Contents.json new file mode 100644 index 0000000..3e606b8 --- /dev/null +++ b/ElegantCalendar.xcassets/brilliantViolet.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.573", + "green" : "0.227", + "red" : "0.271" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ElegantCalendar.xcassets/craftBrown.colorset/Contents.json b/ElegantCalendar.xcassets/craftBrown.colorset/Contents.json new file mode 100644 index 0000000..899f8b3 --- /dev/null +++ b/ElegantCalendar.xcassets/craftBrown.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.388", + "green" : "0.533", + "red" : "0.659" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ElegantCalendar.xcassets/fluorescentPink.colorset/Contents.json b/ElegantCalendar.xcassets/fluorescentPink.colorset/Contents.json new file mode 100644 index 0000000..dbfebe4 --- /dev/null +++ b/ElegantCalendar.xcassets/fluorescentPink.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.302", + "green" : "0.086", + "red" : "0.725" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ElegantCalendar.xcassets/kiwiGreen.colorset/Contents.json b/ElegantCalendar.xcassets/kiwiGreen.colorset/Contents.json new file mode 100644 index 0000000..a63c54e --- /dev/null +++ b/ElegantCalendar.xcassets/kiwiGreen.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.161", + "green" : "0.557", + "red" : "0.459" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ElegantCalendar.xcassets/mauvePurple.colorset/Contents.json b/ElegantCalendar.xcassets/mauvePurple.colorset/Contents.json new file mode 100644 index 0000000..94c8d02 --- /dev/null +++ b/ElegantCalendar.xcassets/mauvePurple.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.451", + "green" : "0.165", + "red" : "0.580" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ElegantCalendar.xcassets/orangeYellow.colorset/Contents.json b/ElegantCalendar.xcassets/orangeYellow.colorset/Contents.json new file mode 100644 index 0000000..39cff7d --- /dev/null +++ b/ElegantCalendar.xcassets/orangeYellow.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.161", + "green" : "0.529", + "red" : "0.859" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ElegantCalendar.xcassets/red.colorset/Contents.json b/ElegantCalendar.xcassets/red.colorset/Contents.json new file mode 100644 index 0000000..cb846c7 --- /dev/null +++ b/ElegantCalendar.xcassets/red.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.110", + "green" : "0.125", + "red" : "0.694" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ElegantCalendar.xcassets/royalBlue.colorset/Contents.json b/ElegantCalendar.xcassets/royalBlue.colorset/Contents.json new file mode 100644 index 0000000..58af0a1 --- /dev/null +++ b/ElegantCalendar.xcassets/royalBlue.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.584", + "green" : "0.325", + "red" : "0.094" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index 09c7f8e..4239cdd 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -16,7 +16,6 @@ 1E029CB424A4450200A81FFD /* ExampleMonthlyCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E029CB124A4450200A81FFD /* ExampleMonthlyCalendarView.swift */; }; 1E029CB524A4450200A81FFD /* ExampleYearlyCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E029CB224A4450200A81FFD /* ExampleYearlyCalendarView.swift */; }; 1E029D3224A4492B00A81FFD /* ElegantPages in Frameworks */ = {isa = PBXBuildFile; productRef = 1E029D3124A4492B00A81FFD /* ElegantPages */; }; - 1E029D3424A44C3700A81FFD /* Color+BlackPearl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E029D3324A44C3700A81FFD /* Color+BlackPearl.swift */; }; 1E628BED24A451B900DDD18E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E628BEC24A451B900DDD18E /* ContentView.swift */; }; 1E628BEF24A4528D00DDD18E /* ExampleSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E628BEE24A4528D00DDD18E /* ExampleSelectionView.swift */; }; 1E628BF124A457C700DDD18E /* Date+FullDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E628BF024A457C700DDD18E /* Date+FullDate.swift */; }; @@ -24,6 +23,10 @@ 1E995AF524A552BA00F436BE /* VisitsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E995AF124A552BA00F436BE /* VisitsListView.swift */; }; 1E995AF624A552BA00F436BE /* VisitPreviewConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E995AF224A552BA00F436BE /* VisitPreviewConstants.swift */; }; 1E995AF724A552BA00F436BE /* VisitCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E995AF324A552BA00F436BE /* VisitCell.swift */; }; + 1EE7C01724BE8720000573A2 /* Calendar+Buildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE7C01624BE8720000573A2 /* Calendar+Buildable.swift */; }; + 1EE7C01924BE8EFD000573A2 /* EnvironmentKey+CalendarTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE7C01824BE8EFD000573A2 /* EnvironmentKey+CalendarTheme.swift */; }; + 1EE7C01B24BEA4B3000573A2 /* ChangeThemeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE7C01A24BEA4B3000573A2 /* ChangeThemeButton.swift */; }; + 1EE7C01D24BEA55B000573A2 /* Color+Custom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE7C01C24BEA55B000573A2 /* Color+Custom.swift */; }; 1EE7DDDA24BD6054008BA2EC /* ElegantCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE7DDB424BD6054008BA2EC /* ElegantCalendarView.swift */; }; 1EE7DDDB24BD6054008BA2EC /* ScrollBackToTodayButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE7DDB624BD6054008BA2EC /* ScrollBackToTodayButton.swift */; }; 1EE7DDDC24BD6054008BA2EC /* MonthlyCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EE7DDB824BD6054008BA2EC /* MonthlyCalendarView.swift */; }; @@ -67,7 +70,6 @@ 1E029CB024A4450200A81FFD /* ExampleCalendarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleCalendarView.swift; sourceTree = ""; }; 1E029CB124A4450200A81FFD /* ExampleMonthlyCalendarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleMonthlyCalendarView.swift; sourceTree = ""; }; 1E029CB224A4450200A81FFD /* ExampleYearlyCalendarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleYearlyCalendarView.swift; sourceTree = ""; }; - 1E029D3324A44C3700A81FFD /* Color+BlackPearl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+BlackPearl.swift"; sourceTree = ""; }; 1E628BEC24A451B900DDD18E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 1E628BEE24A4528D00DDD18E /* ExampleSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleSelectionView.swift; sourceTree = ""; }; 1E628BF024A457C700DDD18E /* Date+FullDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+FullDate.swift"; sourceTree = ""; }; @@ -75,6 +77,10 @@ 1E995AF124A552BA00F436BE /* VisitsListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VisitsListView.swift; sourceTree = ""; }; 1E995AF224A552BA00F436BE /* VisitPreviewConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VisitPreviewConstants.swift; sourceTree = ""; }; 1E995AF324A552BA00F436BE /* VisitCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VisitCell.swift; sourceTree = ""; }; + 1EE7C01624BE8720000573A2 /* Calendar+Buildable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Calendar+Buildable.swift"; sourceTree = ""; }; + 1EE7C01824BE8EFD000573A2 /* EnvironmentKey+CalendarTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EnvironmentKey+CalendarTheme.swift"; sourceTree = ""; }; + 1EE7C01A24BEA4B3000573A2 /* ChangeThemeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeThemeButton.swift; sourceTree = ""; }; + 1EE7C01C24BEA55B000573A2 /* Color+Custom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Custom.swift"; sourceTree = ""; }; 1EE7DDB424BD6054008BA2EC /* ElegantCalendarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElegantCalendarView.swift; sourceTree = ""; }; 1EE7DDB624BD6054008BA2EC /* ScrollBackToTodayButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollBackToTodayButton.swift; sourceTree = ""; }; 1EE7DDB824BD6054008BA2EC /* MonthlyCalendarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MonthlyCalendarView.swift; sourceTree = ""; }; @@ -145,7 +151,7 @@ 1E029D3624A44CBC00A81FFD /* Individual Views */, 1E029D3724A44CF700A81FFD /* Calendar with Accessory View */, 1E029D3524A44CA700A81FFD /* Selection and Exit */, - 1E029D3824A44D2D00A81FFD /* Extensions */, + 1E029D3824A44D2D00A81FFD /* Shared */, 1E029C7C24A4448A00A81FFD /* Assets.xcassets */, 1E029C8124A4448A00A81FFD /* LaunchScreen.storyboard */, 1E029C8424A4448A00A81FFD /* Info.plist */, @@ -192,13 +198,14 @@ path = "Calendar with Accessory View"; sourceTree = ""; }; - 1E029D3824A44D2D00A81FFD /* Extensions */ = { + 1E029D3824A44D2D00A81FFD /* Shared */ = { isa = PBXGroup; children = ( - 1E029D3324A44C3700A81FFD /* Color+BlackPearl.swift */, 1E628BF024A457C700DDD18E /* Date+FullDate.swift */, + 1EE7C01A24BEA4B3000573A2 /* ChangeThemeButton.swift */, + 1EE7C01C24BEA55B000573A2 /* Color+Custom.swift */, ); - path = Extensions; + path = Shared; sourceTree = ""; }; 1EE7DDB124BD6054008BA2EC /* Sources */ = { @@ -296,6 +303,7 @@ 1EE7DDCB24BD6054008BA2EC /* Protocols */ = { isa = PBXGroup; children = ( + 1EE7C01624BE8720000573A2 /* Calendar+Buildable.swift */, 1EE7DDCC24BD6054008BA2EC /* ElegantCalendarCommunicator.swift */, 1EE7DDCD24BD6054008BA2EC /* ElegantCalendarDataSource.swift */, 1EE7DDCE24BD6054008BA2EC /* ElegantCalendarDelegate.swift */, @@ -314,6 +322,7 @@ 1EE7DDD524BD6054008BA2EC /* Color+CustomColors.swift */, 1EE7DDD624BD6054008BA2EC /* Enumeration+Matching.swift */, 1EE7DDD724BD6054008BA2EC /* Calender+Dates.swift */, + 1EE7C01824BE8EFD000573A2 /* EnvironmentKey+CalendarTheme.swift */, ); path = Extensions; sourceTree = ""; @@ -404,13 +413,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1EE7C01D24BEA55B000573A2 /* Color+Custom.swift in Sources */, 1E029CB424A4450200A81FFD /* ExampleMonthlyCalendarView.swift in Sources */, 1EE7DDE024BD6054008BA2EC /* SmallMonthView.swift in Sources */, 1EE7DDE624BD6054008BA2EC /* CalendarConfiguration.swift in Sources */, 1E995AF424A552BA00F436BE /* Visit.swift in Sources */, 1EE7DDE224BD6054008BA2EC /* SmallDayView.swift in Sources */, 1E029C7724A4448A00A81FFD /* AppDelegate.swift in Sources */, + 1EE7C01724BE8720000573A2 /* Calendar+Buildable.swift in Sources */, 1EE7DDDA24BD6054008BA2EC /* ElegantCalendarView.swift in Sources */, + 1EE7C01B24BEA4B3000573A2 /* ChangeThemeButton.swift in Sources */, 1EE7DDEA24BD6054008BA2EC /* MonthlyCalendarManager.swift in Sources */, 1EE7DDDC24BD6054008BA2EC /* MonthlyCalendarView.swift in Sources */, 1EE7DDE924BD6054008BA2EC /* PagerState.swift in Sources */, @@ -420,7 +432,6 @@ 1EE7DDF624BD6054008BA2EC /* LightDarkThemePreview.swift in Sources */, 1EE7DDDE24BD6054008BA2EC /* WeekView.swift in Sources */, 1E029C7924A4448A00A81FFD /* SceneDelegate.swift in Sources */, - 1E029D3424A44C3700A81FFD /* Color+BlackPearl.swift in Sources */, 1EE7DDEB24BD6054008BA2EC /* ElegantCalendarCommunicator.swift in Sources */, 1E995AF624A552BA00F436BE /* VisitPreviewConstants.swift in Sources */, 1EE7DDE424BD6054008BA2EC /* YearView.swift in Sources */, @@ -442,6 +453,7 @@ 1EE7DDF324BD6054008BA2EC /* Color+CustomColors.swift in Sources */, 1EE7DDDB24BD6054008BA2EC /* ScrollBackToTodayButton.swift in Sources */, 1EE7DDF224BD6054008BA2EC /* Date+toString.swift in Sources */, + 1EE7C01924BE8EFD000573A2 /* EnvironmentKey+CalendarTheme.swift in Sources */, 1EE7DDDF24BD6054008BA2EC /* DayView.swift in Sources */, 1EE7DDF024BD6054008BA2EC /* UIImage+BundleInit.swift in Sources */, 1EE7DDEC24BD6054008BA2EC /* ElegantCalendarDataSource.swift in Sources */, @@ -646,7 +658,7 @@ repositoryURL = "https://github.com/ThasianX/ElegantPages"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.0.0; + minimumVersion = 1.2.0; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e61ce66..a74187d 100644 --- a/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/ThasianX/ElegantPages", "state": { "branch": null, - "revision": "450ed57eed98174448929918342889b255c664d1", - "version": "1.1.0" + "revision": "6c356456458b7bf99472577d57cc2560fc3601e2", + "version": "1.2.0" } } ] diff --git a/Example/Example/Calendar with Accessory View/ExampleCalendarView.swift b/Example/Example/Calendar with Accessory View/ExampleCalendarView.swift index 941996f..d77af6e 100644 --- a/Example/Example/Calendar with Accessory View/ExampleCalendarView.swift +++ b/Example/Example/Calendar with Accessory View/ExampleCalendarView.swift @@ -8,12 +8,13 @@ struct ExampleCalendarView: View { let visitsByDay: [Date: [Visit]] + @State private var calendarTheme: CalendarTheme = .royalBlue + init(ascVisits: [Visit]) { let configuration = CalendarConfiguration( calendar: currentCalendar, startDate: ascVisits.first!.arrivalDate, - endDate: ascVisits.last!.arrivalDate, - themeColor: .blackPearl) + endDate: ascVisits.last!.arrivalDate) calendarManager = ElegantCalendarManager( configuration: configuration, @@ -28,7 +29,19 @@ struct ExampleCalendarView: View { } var body: some View { - ElegantCalendarView(calendarManager: calendarManager) + ZStack { + ElegantCalendarView(calendarManager: calendarManager) + .theme(calendarTheme) + VStack { + Spacer() + changeThemeButton + .padding(.bottom, 50) + } + } + } + + private var changeThemeButton: some View { + ChangeThemeButton(calendarTheme: $calendarTheme) } } diff --git a/Example/Example/Calendar with Accessory View/Visit.swift b/Example/Example/Calendar with Accessory View/Visit.swift index d6fb62f..07035d3 100644 --- a/Example/Example/Calendar with Accessory View/Visit.swift +++ b/Example/Example/Calendar with Accessory View/Visit.swift @@ -20,7 +20,7 @@ struct Visit { extension Visit: Identifiable { var id: Int { - locationName.hashValue + UUID().hashValue } } @@ -69,7 +69,7 @@ private extension Calendar { } -fileprivate let colorAssortment: [Color] = [.red, .green, .blue, .gray, .orange, .yellow] +fileprivate let colorAssortment: [Color] = [.turquoise, .forestGreen, .darkPink, .darkRed, .lightBlue, .salmon, .military] private extension Color { @@ -79,3 +79,23 @@ private extension Color { } } + +private extension Color { + + static let turquoise = Color(red: 24, green: 147, blue: 120) + static let forestGreen = Color(red: 22, green: 128, blue: 83) + static let darkPink = Color(red: 179, green: 102, blue: 159) + static let darkRed = Color(red: 185, green: 22, blue: 77) + static let lightBlue = Color(red: 72, green: 147, blue: 175) + static let salmon = Color(red: 219, green: 135, blue: 41) + static let military = Color(red: 117, green: 142, blue: 41) + +} + +fileprivate extension Color { + + init(red: Int, green: Int, blue: Int) { + self.init(red: Double(red)/255, green: Double(green)/255, blue: Double(blue)/255) + } + +} diff --git a/Example/Example/Individual Views/ExampleMonthlyCalendarView.swift b/Example/Example/Individual Views/ExampleMonthlyCalendarView.swift index 636fff4..5700c18 100644 --- a/Example/Example/Individual Views/ExampleMonthlyCalendarView.swift +++ b/Example/Example/Individual Views/ExampleMonthlyCalendarView.swift @@ -8,11 +8,12 @@ struct ExampleMonthlyCalendarView: View { let visitsByDay: [Date: [Visit]] + @State private var calendarTheme: CalendarTheme = .royalBlue + init(ascVisits: [Visit]) { let configuration = CalendarConfiguration(calendar: currentCalendar, startDate: ascVisits.first!.arrivalDate, - endDate: ascVisits.last!.arrivalDate, - themeColor: .blackPearl) + endDate: ascVisits.last!.arrivalDate) calendarManager = MonthlyCalendarManager(configuration: configuration, initialMonth: .daysFromToday(30)) visitsByDay = Dictionary(grouping: ascVisits, by: { currentCalendar.startOfDay(for: $0.arrivalDate) }) @@ -22,7 +23,19 @@ struct ExampleMonthlyCalendarView: View { } var body: some View { - MonthlyCalendarView(calendarManager: calendarManager) + ZStack { + MonthlyCalendarView(calendarManager: calendarManager) + .theme(calendarTheme) + VStack { + Spacer() + changeThemeButton + .padding(.bottom, 50) + } + } + } + + private var changeThemeButton: some View { + ChangeThemeButton(calendarTheme: $calendarTheme) } } diff --git a/Example/Example/Individual Views/ExampleYearlyCalendarView.swift b/Example/Example/Individual Views/ExampleYearlyCalendarView.swift index f7fb65f..af049b1 100644 --- a/Example/Example/Individual Views/ExampleYearlyCalendarView.swift +++ b/Example/Example/Individual Views/ExampleYearlyCalendarView.swift @@ -8,11 +8,12 @@ struct ExampleYearlyCalendarView: View { let visitsByDay: [Date: [Visit]] + @State private var calendarTheme: CalendarTheme = .royalBlue + init(ascVisits: [Visit]) { let configuration = CalendarConfiguration(calendar: currentCalendar, startDate: ascVisits.first!.arrivalDate, - endDate: ascVisits.last!.arrivalDate, - themeColor: .blackPearl) + endDate: ascVisits.last!.arrivalDate) calendarManager = YearlyCalendarManager(configuration: configuration, initialYear: .daysFromToday(365)) visitsByDay = Dictionary(grouping: ascVisits, by: { currentCalendar.startOfDay(for: $0.arrivalDate) }) @@ -21,7 +22,19 @@ struct ExampleYearlyCalendarView: View { } var body: some View { - YearlyCalendarView(calendarManager: calendarManager) + ZStack { + YearlyCalendarView(calendarManager: calendarManager) + .theme(calendarTheme) + VStack { + Spacer() + changeThemeButton + .padding(.bottom, 50) + } + } + } + + private var changeThemeButton: some View { + ChangeThemeButton(calendarTheme: $calendarTheme) } } diff --git a/Example/Example/Selection and Exit/ExampleSelectionView.swift b/Example/Example/Selection and Exit/ExampleSelectionView.swift index 2dc18bc..3af70d2 100644 --- a/Example/Example/Selection and Exit/ExampleSelectionView.swift +++ b/Example/Example/Selection and Exit/ExampleSelectionView.swift @@ -8,7 +8,8 @@ fileprivate let turnAnimation: Animation = .spring(response: 0.4, dampingFractio class SelectionModel: ObservableObject { @Published var showCalendar = false - @Published var calendarManager: ElegantCalendarManager = .init(configuration: .init(startDate: .daysFromToday(-365), endDate: .daysFromToday(365*3), themeColor: .blackPearl)) + @Published var calendarManager: ElegantCalendarManager = .init(configuration: .init(startDate: .daysFromToday(-365), + endDate: .daysFromToday(365*3))) init() { calendarManager.delegate = self diff --git a/Example/Example/Shared/ChangeThemeButton.swift b/Example/Example/Shared/ChangeThemeButton.swift new file mode 100644 index 0000000..39eb2ef --- /dev/null +++ b/Example/Example/Shared/ChangeThemeButton.swift @@ -0,0 +1,26 @@ +// Kevin Li - 7:42 PM - 7/14/20 + +import SwiftUI + +struct ChangeThemeButton: View { + + @Binding var calendarTheme: CalendarTheme + + var body: some View { + Button(action: { + self.calendarTheme = .randomTheme + }) { + Text("CHANGE THEME") + } + } + +} + +private extension CalendarTheme { + + static var randomTheme: CalendarTheme { + let randomNumber = arc4random_uniform(UInt32(CalendarTheme.allThemes.count)) + return CalendarTheme.allThemes[Int(randomNumber)] + } + +} diff --git a/Example/Example/Extensions/Color+BlackPearl.swift b/Example/Example/Shared/Color+Custom.swift similarity index 73% rename from Example/Example/Extensions/Color+BlackPearl.swift rename to Example/Example/Shared/Color+Custom.swift index cef803d..98d640a 100644 --- a/Example/Example/Extensions/Color+BlackPearl.swift +++ b/Example/Example/Shared/Color+Custom.swift @@ -1,4 +1,4 @@ -// Kevin Li - 8:06 PM - 6/24/20 +// Kevin Li - 7:45 PM - 7/14/20 import SwiftUI diff --git a/Example/Example/Extensions/Date+FullDate.swift b/Example/Example/Shared/Date+FullDate.swift similarity index 100% rename from Example/Example/Extensions/Date+FullDate.swift rename to Example/Example/Shared/Date+FullDate.swift diff --git a/Package.resolved b/Package.resolved index e61ce66..a74187d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/ThasianX/ElegantPages", "state": { "branch": null, - "revision": "450ed57eed98174448929918342889b255c664d1", - "version": "1.1.0" + "revision": "6c356456458b7bf99472577d57cc2560fc3601e2", + "version": "1.2.0" } } ] diff --git a/Package.swift b/Package.swift index b594581..0f0aaa6 100644 --- a/Package.swift +++ b/Package.swift @@ -13,7 +13,7 @@ let package = Package( targets: ["ElegantCalendar"]), ], dependencies: [ - .package(url: "https://github.com/ThasianX/ElegantPages", .upToNextMajor(from: "1.1.0")) + .package(url: "https://github.com/ThasianX/ElegantPages", from: "1.2.0") ], targets: [ .target( diff --git a/README.md b/README.md index 1a34591..c921062 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,18 @@ ElegantCalendar is an efficient and customizable full screen calendar written in +### Comes with 8 default themes. You can also configure your own theme. Read more to find out. + +

+ + + + + + + +

+ - [Introduction](#introduction) - [Basic Usage](#basic-usage) - [How It Works](#how-it-works) @@ -43,6 +55,7 @@ Features: * Flexibility in either using the full calendar view that has both the monthly and yearly view or just one of the individual views * Haptics when performing certain actions * Intuitive navigation between the yearly and monthly view: swipe between views or tap on the month header to navigate to the yearly view +* Elegant default themes ## Basic usage @@ -57,8 +70,7 @@ struct ExampleCalendarView: View { @ObservedObject var calendarManager = ElegantCalendarManager( configuration: CalendarConfiguration(startDate: startDate, - endDate: endDate, - themeColor: .blackPearl)) + endDate: endDate)) var body: some View { ElegantCalendarView(calendarManager: calendarManager) @@ -77,8 +89,7 @@ struct ExampleMonthlyCalendarView: View { @ObservedObject var calendarManager = MonthlyCalendarManager( configuration: CalendarConfiguration(startDate: startDate, - endDate: endDate, - themeColor: .blackPearl)) + endDate: endDate)) var body: some View { MonthlyCalendarView(calendarManager: calendarManager) @@ -90,8 +101,7 @@ struct ExampleYearlyCalendarView: View { @ObservedObject var calendarManager = YearlyCalendarManager( configuration: CalendarConfiguration(startDate: startDate, - endDate: endDate, - themeColor: .blackPearl)) + endDate: endDate)) var body: some View { YearlyCalendarView(calendarManager: calendarManager) @@ -111,7 +121,7 @@ So how can this be fixed? Either create a simpler yearly calendar that doesn't r ## Customization -The following aspects of `ElegantCalendarManager` can be customized: +### `ElegantCalendarManager` #### `configuration`: The configuration of the calendar view @@ -121,10 +131,8 @@ public struct CalendarConfiguration: Equatable { let calendar: Calendar let ascending: Bool // reverses the order in which the calendar is laid out - let allowHaptics: Bool let startDate: Date let endDate: Date - let themeColor: Color // if you want to support light and dark mode, make sure your color does so as well } @@ -176,6 +184,52 @@ public protocol YearlyCalendarDelegate { This is just a convenience to handle the shortcomings of the `@Published` wrapper which doesn't support `didSet`. Conform to this if you need to do things when a month is displayed or date changes. + +### `ElegantCalendarView` & `YearlyCalendarView` & `MonthlyCalendarView` + +#### `theme`: The theme of various components of the calendar. Default is royal blue. + +```swift + +public struct CalendarTheme: Equatable, Hashable { + + let primary: Color + +} + +public extension CalendarTheme { + + static let brilliantViolet = CalendarTheme(primary: .brilliantViolet) + static let craftBrown = CalendarTheme(primary: .craftBrown) + static let fluorescentPink = CalendarTheme(primary: .fluorescentPink) + static let kiwiGreen = CalendarTheme(primary: .kiwiGreen) + static let mauvePurple = CalendarTheme(primary: .mauvePurple) + static let orangeYellow = CalendarTheme(primary: .orangeYellow) + static let red = CalendarTheme(primary: .red) + static let royalBlue = CalendarTheme(primary: .royalBlue) + +} + +ElegantCalendarView(...) + .theme(.mauvePurple) + +``` + +To configure your own theme, just pass in your color into the `CalendarTheme` initializer. To have dynamic appearance, make sure your `Color` has both a light and dark appearance. + +### `ElegantCalendarView` & `MonthlyCalendarView` + +#### `allowsHaptics`: Whether haptics is enabled or not. Default is enabled. + +```swift + +ElegantCalendarView(...) + .allowsHaptics(false) + +``` + +Users get haptics whenever they tap a day, scroll to a new month, or press the scroll back to today button. + ## Use Cases The following aspects of `ElegantCalendarManager` can be used: @@ -209,7 +263,7 @@ If you are using `Package.swift`, you can also add `ElegantCalendar` as a depend let package = Package( name: "TestProject", dependencies: [ - .package(url: "https://github.com/ThasianX/ElegantCalendar", from: "3.0.0") + .package(url: "https://github.com/ThasianX/ElegantCalendar", from: "4.0.0") ], targets: [ .target(name: "TestProject", dependencies: ["ElegantCalendar"]) diff --git a/Screenshots/brilliantViolet.PNG b/Screenshots/brilliantViolet.PNG new file mode 100644 index 0000000000000000000000000000000000000000..8e180e8d18eed60f67a1a86ae819603d155b1921 GIT binary patch literal 227995 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