Skip to content

Commit

Permalink
Feature/startup themepicker (#14)
Browse files Browse the repository at this point in the history
* added initial iteration of the startup themepicker

* added an onappear animation

* fixed bug where the cells would refresh everytime a new theme was picked and also since the timer was still active at setup

* used an offwhite color as the initial setup color. better constrast for the user. also fixed a bug where the initial theme color in the setup would get overriden by the setting's theme picker. so now, the setting's theme picker is lazily initialized

* uses new color palette library

* added a completed startup theme picker

* added customizable spawn size and increased the rotation speed. also added a scale effect for nodes being focused

* lowered focus speed

* updated package

* added theme picker image

* Update README.md

* updated paging theme image
  • Loading branch information
ThasianX authored Aug 6, 2020
1 parent 43612fe commit f5624e4
Show file tree
Hide file tree
Showing 16 changed files with 330 additions and 31 deletions.
26 changes: 25 additions & 1 deletion ElegantTimeline.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
1E161B0624D5FF5000CA2B7F /* ThemePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E161B0524D5FF5000CA2B7F /* ThemePickerView.swift */; };
1E1EBE0424AF0C5E00FBBD41 /* Calendar+DateOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E1EBE0324AF0C5E00FBBD41 /* Calendar+DateOperations.swift */; };
1E5017FB2488B4C1002496DE /* View+CaptureSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E5017FA2488B4C1002496DE /* View+CaptureSize.swift */; };
1E66931924D9D54900887BE4 /* StartupThemePickerOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E66931824D9D54900887BE4 /* StartupThemePickerOverlay.swift */; };
1E66EC3824AC19CD0070F16E /* EnvironmentKey+Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E66EC3724AC19CD0070F16E /* EnvironmentKey+Timer.swift */; };
1E67992924AEB1C300C18735 /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E67992824AEB1C300C18735 /* MenuView.swift */; };
1E717C6E24B9970D0040877E /* VisitsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E717C6D24B9970D0040877E /* VisitsProvider.swift */; };
1E7332EF24DA0F9B0010C8E2 /* AppTheme+PaletteColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E7332EE24DA0F9B0010C8E2 /* AppTheme+PaletteColor.swift */; };
1E7332F124DA454A0010C8E2 /* EnvironmentKey+IsSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E7332F024DA454A0010C8E2 /* EnvironmentKey+IsSetup.swift */; };
1E80E5BF24BD727B0030F5E0 /* Comparable+Clamped.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E80E5BE24BD727B0030F5E0 /* Comparable+Clamped.swift */; };
1E80E5C124BD770C0030F5E0 /* Image+Custom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E80E5C024BD770C0030F5E0 /* Image+Custom.swift */; };
1E854E3124AECC6300E50973 /* Color+Custom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E854E3024AECC6300E50973 /* Color+Custom.swift */; };
Expand All @@ -38,6 +41,7 @@
1E8A3E0D24859F7000CBA65E /* VisitPreviewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8A3E0C24859F7000CBA65E /* VisitPreviewCell.swift */; };
1E8A3E0F2485AAE000CBA65E /* DayPreviewBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8A3E0E2485AAE000CBA65E /* DayPreviewBlock.swift */; };
1E8A3E112485AEAD00CBA65E /* EnvironmentKey+AppTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8A3E102485AEAD00CBA65E /* EnvironmentKey+AppTheme.swift */; };
1E8C6D7124DB3DED00FAEAED /* StartupThemePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8C6D7024DB3DED00FAEAED /* StartupThemePicker.swift */; };
1E8D768724B813250027073E /* VisitsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8D768624B813250027073E /* VisitsListView.swift */; };
1E8D768924B813C10027073E /* VisitCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8D768824B813C10027073E /* VisitCell.swift */; };
1E8D768B24B816A50027073E /* PageScrollState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E8D768A24B816A40027073E /* PageScrollState.swift */; };
Expand Down Expand Up @@ -68,9 +72,12 @@
1E161B0524D5FF5000CA2B7F /* ThemePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemePickerView.swift; sourceTree = "<group>"; };
1E1EBE0324AF0C5E00FBBD41 /* Calendar+DateOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Calendar+DateOperations.swift"; sourceTree = "<group>"; };
1E5017FA2488B4C1002496DE /* View+CaptureSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+CaptureSize.swift"; sourceTree = "<group>"; };
1E66931824D9D54900887BE4 /* StartupThemePickerOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartupThemePickerOverlay.swift; sourceTree = "<group>"; };
1E66EC3724AC19CD0070F16E /* EnvironmentKey+Timer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EnvironmentKey+Timer.swift"; sourceTree = "<group>"; };
1E67992824AEB1C300C18735 /* MenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = "<group>"; };
1E717C6D24B9970D0040877E /* VisitsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisitsProvider.swift; sourceTree = "<group>"; };
1E7332EE24DA0F9B0010C8E2 /* AppTheme+PaletteColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppTheme+PaletteColor.swift"; sourceTree = "<group>"; };
1E7332F024DA454A0010C8E2 /* EnvironmentKey+IsSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EnvironmentKey+IsSetup.swift"; sourceTree = "<group>"; };
1E80E5BE24BD727B0030F5E0 /* Comparable+Clamped.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Comparable+Clamped.swift"; sourceTree = "<group>"; };
1E80E5C024BD770C0030F5E0 /* Image+Custom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Image+Custom.swift"; sourceTree = "<group>"; };
1E854E3024AECC6300E50973 /* Color+Custom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Custom.swift"; sourceTree = "<group>"; };
Expand All @@ -93,6 +100,7 @@
1E8A3E0C24859F7000CBA65E /* VisitPreviewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisitPreviewCell.swift; sourceTree = "<group>"; };
1E8A3E0E2485AAE000CBA65E /* DayPreviewBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayPreviewBlock.swift; sourceTree = "<group>"; };
1E8A3E102485AEAD00CBA65E /* EnvironmentKey+AppTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EnvironmentKey+AppTheme.swift"; sourceTree = "<group>"; };
1E8C6D7024DB3DED00FAEAED /* StartupThemePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartupThemePicker.swift; sourceTree = "<group>"; };
1E8D768624B813250027073E /* VisitsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisitsListView.swift; sourceTree = "<group>"; };
1E8D768824B813C10027073E /* VisitCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisitCell.swift; sourceTree = "<group>"; };
1E8D768A24B816A40027073E /* PageScrollState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageScrollState.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -180,6 +188,15 @@
path = Helpers;
sourceTree = "<group>";
};
1E8C6D7224DB5BB200FAEAED /* StartupThemePickerOverlay */ = {
isa = PBXGroup;
children = (
1E66931824D9D54900887BE4 /* StartupThemePickerOverlay.swift */,
1E8C6D7024DB3DED00FAEAED /* StartupThemePicker.swift */,
);
path = StartupThemePickerOverlay;
sourceTree = "<group>";
};
1E8D768C24B816FA0027073E /* VisitsListView */ = {
isa = PBXGroup;
children = (
Expand All @@ -202,6 +219,7 @@
isa = PBXGroup;
children = (
1E15AC6E24AEA01800623E98 /* HomeView.swift */,
1E8C6D7224DB5BB200FAEAED /* StartupThemePickerOverlay */,
1E995B0224A69C3C00F436BE /* VisitsPreviewView */,
1E67992824AEB1C300C18735 /* MenuView.swift */,
1E161B0524D5FF5000CA2B7F /* ThemePickerView.swift */,
Expand All @@ -225,6 +243,7 @@
1EAAC53C24875D26006DBB8E /* Extensions */ = {
isa = PBXGroup;
children = (
1E7332EE24DA0F9B0010C8E2 /* AppTheme+PaletteColor.swift */,
1E1EBE0324AF0C5E00FBBD41 /* Calendar+DateOperations.swift */,
1E854E3024AECC6300E50973 /* Color+Custom.swift */,
1E80E5BE24BD727B0030F5E0 /* Comparable+Clamped.swift */,
Expand All @@ -236,6 +255,7 @@
1EAAC5342485DBED006DBB8E /* DateComponents+Subcomponents.swift */,
1E8A3E0224858AF900CBA65E /* Dictionary+SortedKeys.swift */,
1E8A3E102485AEAD00CBA65E /* EnvironmentKey+AppTheme.swift */,
1E7332F024DA454A0010C8E2 /* EnvironmentKey+IsSetup.swift */,
1E66EC3724AC19CD0070F16E /* EnvironmentKey+Timer.swift */,
1E80E5C024BD770C0030F5E0 /* Image+Custom.swift */,
1E5017FA2488B4C1002496DE /* View+CaptureSize.swift */,
Expand Down Expand Up @@ -401,6 +421,7 @@
1E67992924AEB1C300C18735 /* MenuView.swift in Sources */,
1EE035E824B298B500DE6850 /* UITableViewDirectAccess.swift in Sources */,
1EAAC5352485DBED006DBB8E /* DateComponents+Subcomponents.swift in Sources */,
1E7332F124DA454A0010C8E2 /* EnvironmentKey+IsSetup.swift in Sources */,
1EFC6BC724BFE197002B96D7 /* Date+DaysFromToday.swift in Sources */,
1EAAC5332485C5A0006DBB8E /* Date+toDateComponents.swift in Sources */,
1E8A3E0524859ECB00CBA65E /* DaySideBar.swift in Sources */,
Expand All @@ -409,10 +430,13 @@
1E995AFB24A5845900F436BE /* FromTodayPopupView.swift in Sources */,
1E8D768924B813C10027073E /* VisitCell.swift in Sources */,
1E66EC3824AC19CD0070F16E /* EnvironmentKey+Timer.swift in Sources */,
1E8C6D7124DB3DED00FAEAED /* StartupThemePicker.swift in Sources */,
1E8A3DFC24855F0600CBA65E /* VisitsPreviewView.swift in Sources */,
1E66931924D9D54900887BE4 /* StartupThemePickerOverlay.swift in Sources */,
1EC22FE824C03F470095F3C3 /* FromTodayPopupState.swift in Sources */,
1E995B0424A69C7300F436BE /* VisitsTimelineView.swift in Sources */,
1E8A3E0924859EF400CBA65E /* MonthYearSideBar.swift in Sources */,
1E7332EF24DA0F9B0010C8E2 /* AppTheme+PaletteColor.swift in Sources */,
1EAAC53B24875D14006DBB8E /* DateComponents+Strideable.swift in Sources */,
1E995AFF24A5CBEF00F436BE /* DayVisitsView.swift in Sources */,
1E8A3E0724859EE200CBA65E /* Constants.swift in Sources */,
Expand Down Expand Up @@ -649,7 +673,7 @@
repositoryURL = "https://github.com/ThasianX/ElegantColorPalette";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.1.0;
minimumVersion = 1.2.0;
};
};
/* End XCRemoteSwiftPackageReference section */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@
"repositoryURL": "https://github.com/ThasianX/ElegantCalendar",
"state": {
"branch": null,
"revision": "112afd0e1c81edc3d4cc222e237359250548eaa0",
"version": "4.2.0"
"revision": "64e422511f1620aaa4744acd1c345e7d6e2f5a29",
"version": "4.3.0"
}
},
{
"package": "ElegantColorPalette",
"repositoryURL": "https://github.com/ThasianX/ElegantColorPalette",
"state": {
"branch": null,
"revision": "4abfee7b82a31f664795865de937f8713c236565",
"version": "1.1.0"
"revision": "cf8628ad84314db868659db57cafb3e1403b3fe6",
"version": "1.2.0"
}
},
{
Expand Down
13 changes: 13 additions & 0 deletions ElegantTimeline/Helpers/Extensions/AppTheme+PaletteColor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Kevin Li - 2:50 PM - 8/4/20

import ElegantColorPalette

extension AppTheme {

static let allPaletteColors = Self.allThemes.map { PaletteColor(name: $0.name, uiColor: $0.primaryuiColor) }

static func theme(for paletteColor: PaletteColor) -> AppTheme {
Self.allThemes.first(where: { $0.name == paletteColor.name })!
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ extension AppTheme {
static let wednesdayPink = AppTheme(name: "Wednesday Pink",
primaryuiColor: .wednesdayPink,
complementaryuiColor: .wednesdayPinkComplement)

// Dummy color used for the initial color theme selection. Not actually part of the themes.
static let _white = AppTheme(name: "Off White",
primaryuiColor: ._white,
complementaryuiColor: ._whiteComplement)
}

extension UIColor {
Expand Down Expand Up @@ -150,6 +155,10 @@ extension UIColor {
static let underwaterBlueComplement = UIColor(red: 24, green: 136, blue: 146)
static let wednesdayPinkComplement = UIColor(red: 171, green: 98, blue: 153)

// Dummy color used for the initial color theme selection. Not actually part of the themes.
static let _white = UIColor(red: 245, green: 250, blue: 250)
static let _whiteComplement = UIColor(red: 240, green: 245, blue: 246)

}

fileprivate extension UIColor {
Expand Down
18 changes: 18 additions & 0 deletions ElegantTimeline/Helpers/Extensions/EnvironmentKey+IsSetup.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Kevin Li - 6:39 PM - 8/4/20

import SwiftUI

struct IsSetupKey: EnvironmentKey {

static var defaultValue: Bool = true

}

extension EnvironmentValues {

var isSetup: Bool {
get { self[IsSetupKey.self] }
set { self[IsSetupKey.self] = newValue }
}

}
10 changes: 8 additions & 2 deletions ElegantTimeline/Helpers/ObservableObjects/HomeManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import SwiftUI
class HomeManager: ObservableObject {

@Published var scrollState: PageScrollState = .init()
@Published var appTheme: AppTheme = .royalBlue
@Published var calendarTheme: CalendarTheme = .royalBlue
@Published var appTheme: AppTheme = ._white
@Published var calendarTheme: CalendarTheme = ._white

let visitsProvider: VisitsProvider
let listScrollState: ListScrollState
Expand Down Expand Up @@ -174,3 +174,9 @@ extension HomeManagerDirectAccess {
}

}

private extension CalendarTheme {

static let _white = CalendarTheme(primary: Color(._white))

}
44 changes: 35 additions & 9 deletions ElegantTimeline/Views/HomeView/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,27 @@ struct HomeView: View, HomeManagerDirectAccess {
@ObservedObject var manager: HomeManager
@GestureState var stateTransaction: PageScrollState.TransactionInfo

@State private var isSetup: Bool = true

init(manager: HomeManager) {
self.manager = manager
_stateTransaction = manager.scrollState.horizontalGestureState
}

var body: some View {
horizontalPagingStack
.contentShape(Rectangle())
.frame(width: pageWidth, alignment: .leading)
.offset(x: pageOffset)
.offset(x: boundedTranslation)
.simultaneousGesture(pagingGesture, including: gesturesToMask)
ZStack {
horizontalPagingStack
.contentShape(Rectangle())
.frame(width: pageWidth, alignment: .leading)
.offset(x: pageOffset)
.offset(x: boundedTranslation)
.simultaneousGesture(pagingGesture, including: gesturesToMask)
if isSetup {
// The animations are all contained in the overlay. The state of
// whether the overlay is active or not is just to conserve memory
themePickerOverlay
}
}
}

}
Expand All @@ -43,9 +52,12 @@ private extension HomeView {
.zIndex(1) // Should take overlapping precedence over the menu view
menuView
.offset(x: menuOffset)
themePickerView
.offset(x: themePickerOffset)
.zIndex(1) // Should take overlapping precedence over the menu and visits view
if !isSetup {
// lazily setup the app's theme picker view after the setup
themePickerView
.offset(x: themePickerOffset)
.zIndex(1) // Should take overlapping precedence over the menu and visits view
}
}
.environmentObject(scrollState)
}
Expand Down Expand Up @@ -101,6 +113,7 @@ private extension HomeView {
VisitsPreviewView(visitsProvider: visitsProvider,
listScrollState: listScrollState)
.environment(\.appTheme, appTheme)
.environment(\.isSetup, isSetup)
}

var menuView: some View {
Expand All @@ -111,6 +124,19 @@ private extension HomeView {
ThemePickerView(currentTheme: appTheme, changeTheme: changeTheme)
}

var themePickerOverlay: some View {
StartupThemePickerOverlay(onThemeSelected: changeListTheme, onFinalize: hideOverlay)
}

func changeListTheme(_ appTheme: AppTheme) {
manager.appTheme = appTheme
}

func hideOverlay() {
manager.calendarTheme = CalendarTheme(primary: manager.appTheme.primary)
isSetup = false
}

}

private extension HomeView {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Kevin Li - 12:20 PM - 8/5/20

import ElegantColorPalette
import SpriteKit
import SwiftUI

struct StartupThemePicker: UIViewRepresentable {

typealias UIViewType = ColorPaletteView

@Binding var selectedColor: PaletteColor?
let colors: [PaletteColor]
let didSelectColor: (PaletteColor) -> Void

let showExitAnimation: Bool
let exitDuration: TimeInterval

func makeUIView(context: Context) -> ColorPaletteView {
ColorPaletteView(colors: colors, selectedColor: selectedColor)
.didSelectColor(groupedCallback)
.nodeStyle(NoNameNodeStyle())
.canMoveFocusedNode(false)
.rotation(multiplier: 5)
.spawnConfiguration(widthRatio: 1, heightRatio: 1)
.focus(speed: 1000)
}

private func groupedCallback(_ color: PaletteColor) {
bindingCallback(color)
didSelectColor(color)
}

private func bindingCallback(_ color: PaletteColor) {
DispatchQueue.main.async {
withAnimation(.easeInOut(duration: 0.5)) {
self.selectedColor = color
}
}
}

func updateUIView(_ uiView: ColorPaletteView, context: Context) {
if showExitAnimation {
beginExitAnimation(forScene: uiView.paletteScene)
}
}

private func beginExitAnimation(forScene scene: ColorPaletteScene) {
guard let selectedNode = scene.selectedColorNode else { return }

scaleAndFadeNode(selectedNode)

scene.allColorNodes
.filter { $0 != selectedNode }
.forEach { node in
pushNodeOutOfBounds(node)
}
}

private func scaleAndFadeNode(_ node: ColorNode) {
node.physicsBody?.isDynamic = false

let scaleAction = SKAction.scale(to: 5, duration: exitDuration)
let fadeAction = SKAction.fadeOut(withDuration: exitDuration)
let scaleFadeAction = SKAction.group([scaleAction, fadeAction])
node.run(scaleFadeAction)
}

private func pushNodeOutOfBounds(_ node: ColorNode) {
// Basically makes the node not collide with anything -> pushes out of screen bounds
node.physicsBody?.collisionBitMask = .zero

let positionVector = CGVector(dx: node.position.x, dy: node.position.y)
let pushVector = CGVector(dx: positionVector.dx*10, dy: positionVector.dy*10)
node.physicsBody?.velocity = pushVector
}

}

private struct NoNameNodeStyle: NodeStyle {

func updateNode(configuration: Configuration) -> ColorNode {
configuration.node
.scaleFade(!configuration.isFirstShown,
scale: configuration.isPressed ? 0.9 : (configuration.isFocusing ? 1.3 : 1),
opacity: configuration.isPressed ? 0.3 : 1)
.startUp(configuration.isFirstShown)
}

}
Loading

0 comments on commit f5624e4

Please sign in to comment.