Skip to content

Commit

Permalink
Merge pull request #334 from aapis/feature/1.18/weekataglance-and-tim…
Browse files Browse the repository at this point in the history
…eline-ux

"Week at a glance" widget, Timeline, and Search UI/UX improvements
  • Loading branch information
aapis authored Nov 11, 2024
2 parents fb1d7f8 + c187c59 commit 4e19edb
Show file tree
Hide file tree
Showing 20 changed files with 501 additions and 290 deletions.
44 changes: 43 additions & 1 deletion KWCore/Sources/Query/CDSavedSearch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,24 @@ public class CDSavedSearch: ObservableObject {
self.moc = moc
}

/// Find search terms created on a given date
/// - Parameter date: Date
/// - Returns: FetchRequest<SavedSearch>
static public func createdBetween(_ start: Date?, _ end: Date?) -> FetchRequest<SavedSearch> {
let fetch: NSFetchRequest<SavedSearch> = SavedSearch.fetchRequest()
fetch.predicate = NSPredicate(
format: "created > %@ && created < %@",
(start ?? Date.now) as CVarArg,
(end ?? Date.now) as CVarArg
)
fetch.sortDescriptors = [
NSSortDescriptor(keyPath: \SavedSearch.term, ascending: false),
NSSortDescriptor(keyPath: \SavedSearch.created, ascending: false)
]

return FetchRequest(fetchRequest: fetch, animation: .easeInOut)
}

/// Find all saved searches
/// - Returns: [SavedSearch]
public func all() -> [SavedSearch] {
Expand All @@ -33,7 +51,21 @@ public class CDSavedSearch: ObservableObject {

return self.query(predicate)
}


/// Find all saved searches
/// - Parameter start: Date
/// - Parameter end: Date
/// - Returns: [SavedSearch]
public func createdBetween(_ start: Date?, end: Date?) -> [SavedSearch] {
return self.query(
NSPredicate(
format: "created > %@ && created < %@",
(start ?? Date.now) as CVarArg,
(end ?? Date.now) as CVarArg
)
)
}

/// Find SavedSearch objects by term
/// - Parameter term: String
/// - Returns: Optional(SavedSearch
Expand Down Expand Up @@ -70,7 +102,16 @@ public class CDSavedSearch: ObservableObject {
public func destroy(_ term: String) -> Void {
if let entity = self.query(NSPredicate(format: "term == %@", term)).first {
self.moc!.delete(entity)
PersistenceController.shared.save()
}
}

/// Destroy a saved search term
/// - Parameter term: SavedSearch
/// - Returns: Void
public func unpublish(_ term: String) -> Void {
if let entity = self.query(NSPredicate(format: "term == %@", term)).first {
entity.alive = false
PersistenceController.shared.save()
}
}
Expand Down Expand Up @@ -105,6 +146,7 @@ public class CDSavedSearch: ObservableObject {
let savedSearch = SavedSearch(context: self.moc!)
savedSearch.term = term
savedSearch.created = created
savedSearch.alive = true

if saveByDefault {
PersistenceController.shared.save()
Expand Down
16 changes: 8 additions & 8 deletions KWCore/Sources/Query/CoreDataCompanies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ public class CoreDataCompanies: ObservableObject {
set.insert(entity)
}

return Array(set).sorted(by: {$0.lastUpdate ?? Date() > $1.lastUpdate ?? Date()})
return Array(set).sorted(by: {$0.lastUpdate ?? Date() < $1.lastUpdate ?? Date()})
}

/// Find all interactions within a certain date range
Expand Down Expand Up @@ -353,7 +353,7 @@ public class CoreDataCompanies: ObservableObject {
set.insert(entity)
}

return Array(set).sorted(by: {$0.lastUpdate ?? Date() > $1.lastUpdate ?? Date()})
return Array(set).sorted(by: {$0.lastUpdate ?? Date() < $1.lastUpdate ?? Date()})
}

return []
Expand Down Expand Up @@ -466,16 +466,16 @@ public class CoreDataCompanies: ObservableObject {
/// Query companies
/// - Parameter predicate: Query predicate
/// - Returns: Array<Company>
private func query(_ predicate: NSPredicate? = nil) -> [Company] {
private func query(_ predicate: NSPredicate? = nil, sort: [NSSortDescriptor] = [
NSSortDescriptor(keyPath: \Company.isDefault, ascending: false),
NSSortDescriptor(keyPath: \Company.name?, ascending: true),
NSSortDescriptor(keyPath: \Company.createdDate?, ascending: true)
]) -> [Company] {
lock.lock()

var results: [Company] = []
let fetch: NSFetchRequest<Company> = Company.fetchRequest()
fetch.sortDescriptors = [
NSSortDescriptor(keyPath: \Company.isDefault, ascending: false),
NSSortDescriptor(keyPath: \Company.name?, ascending: true),
NSSortDescriptor(keyPath: \Company.createdDate?, ascending: true)
]
fetch.sortDescriptors = sort

if predicate != nil {
fetch.predicate = predicate
Expand Down
8 changes: 4 additions & 4 deletions KWCore/Sources/Query/CoreDataJob.swift
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ public class CoreDataJob: ObservableObject {
set.insert(entity)
}

return Array(set).sorted(by: {$0.lastUpdate ?? Date() > $1.lastUpdate ?? Date()})
return Array(set).sorted(by: {$0.lastUpdate ?? Date() < $1.lastUpdate ?? Date()})
}

/// Find all interactions within a certain date range
Expand Down Expand Up @@ -408,7 +408,7 @@ public class CoreDataJob: ObservableObject {
set.insert(entity)
}

return Array(set).sorted(by: {$0.lastUpdate ?? Date() > $1.lastUpdate ?? Date()})
return Array(set).sorted(by: {$0.lastUpdate ?? Date() < $1.lastUpdate ?? Date()})
}

return []
Expand Down Expand Up @@ -619,12 +619,12 @@ public class CoreDataJob: ObservableObject {
return newJob
}

private func query(_ predicate: NSPredicate) -> [Job] {
private func query(_ predicate: NSPredicate, sort: [NSSortDescriptor] = [NSSortDescriptor(keyPath: \Job.created?, ascending: true)]) -> [Job] {
lock.lock()

var results: [Job] = []
let fetch: NSFetchRequest<Job> = Job.fetchRequest()
fetch.sortDescriptors = [NSSortDescriptor(keyPath: \Job.created?, ascending: true)]
fetch.sortDescriptors = sort
fetch.predicate = predicate
fetch.returnsDistinctResults = true

Expand Down
4 changes: 2 additions & 2 deletions KWCore/Sources/Query/CoreDataProjects.swift
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ public class CoreDataProjects: ObservableObject {
set.insert(entity)
}

return Array(set).sorted(by: {$0.lastUpdate ?? Date() > $1.lastUpdate ?? Date()})
return Array(set).sorted(by: {$0.lastUpdate ?? Date() < $1.lastUpdate ?? Date()})
}

/// Find all interactions within a certain date range
Expand Down Expand Up @@ -398,7 +398,7 @@ public class CoreDataProjects: ObservableObject {
set.insert(entity)
}

return Array(set).sorted(by: {$0.lastUpdate ?? Date() > $1.lastUpdate ?? Date()})
return Array(set).sorted(by: {$0.lastUpdate ?? Date() < $1.lastUpdate ?? Date()})
}

return []
Expand Down
50 changes: 50 additions & 0 deletions KWCore/Sources/UI/WidgetLibrary/WidgetLibrary.UI.Buttons.swift
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,56 @@ extension WidgetLibrary.UI {
.useDefaultHover({ hover in self.isHighlighted = hover })
}
}

// MARK: SavedSearchTerm
struct SavedSearchTerm: View {
@EnvironmentObject public var state: Navigation
public var savedSearch: SavedSearch
@State private var isHighlighted: Bool = false

var body: some View {
Button {
self.state.to(.dashboard)
if let term = self.savedSearch.term {
self.state.session.search.text = term
}
} label: {
HStack {
Text(savedSearch.term ?? "Invalid term name")
Spacer()
if let timestamp = savedSearch.created?.formatted(date: .abbreviated, time: .shortened) {
Timestamp(text: timestamp, alignment: .trailing)
}
}
.padding(8)
.background(.white.opacity(self.isHighlighted ? 0.07 : 0.03))
.useDefaultHover({ hover in self.isHighlighted = hover })
.clipShape(.rect(cornerRadius: 5))
}
.buttonStyle(.plain)
.help("Searched for term \(savedSearch.created?.formatted(date: .complete, time: .complete) ?? "at some point in history")")
.contextMenu {
VStack {
Button {
self.state.to(.timeline)
if let date = self.savedSearch.created {
self.state.session.date = date
}
} label: {
Text("Show Timeline...")
}
Button {
self.state.to(.today)
if let date = self.savedSearch.created {
self.state.session.date = date
}
} label: {
Text("Show Today...")
}
}
}
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,38 +22,23 @@ extension WidgetLibrary.UI {
@AppStorage("today.endOfDay") public var endOfDay: Int = 18
public var start: Date?
@State private var days: [DayBlock] = []
private var columns: [GridItem] { Array(repeating: GridItem(.flexible(), spacing: 1), count: 7) }

var body: some View {
NavigationStack {
Grid(alignment: .topLeading, horizontalSpacing: 5, verticalSpacing: 0) {
GridRow {
HStack(alignment: .center) {
UI.ListLinkTitle(text: "Week at a Glance")
Spacer()
UI.Buttons.Minimize(isMinimized: $isWeekAtAGlanceMinimized)
}
}
.padding([.leading, .trailing], 8)
.background(self.isWeekAtAGlanceMinimized ? Theme.textBackground : .clear)
.clipShape(.rect(topLeadingRadius: 5, bottomLeadingRadius: self.isWeekAtAGlanceMinimized ? 5 : 0, bottomTrailingRadius: self.isWeekAtAGlanceMinimized ? 5 : 0, topTrailingRadius: 5))

if !self.isWeekAtAGlanceMinimized {
GridRow(alignment: .top) {
ZStack(alignment: .top) {
LinearGradient(colors: [Theme.base, .clear], startPoint: .top, endPoint: .bottom)
.opacity(0.3)
.blendMode(.softLight)
.frame(height: 50)
Divider()
LazyVGrid(columns: self.columns, alignment: .leading) {
ForEach(self.days, id: \.id) { day in day }
}
.padding()
}
.clipShape(.rect(cornerRadius: 5))
}
VStack {
HStack(alignment: .center) {
UI.ListLinkTitle(text: "Week at a Glance")
Spacer()
UI.Buttons.Minimize(isMinimized: $isWeekAtAGlanceMinimized)
}
.padding(8)
.background(self.isWeekAtAGlanceMinimized ? self.state.session.appPage.primaryColour : .clear)
.clipShape(.rect(cornerRadius: 5))

if !self.isWeekAtAGlanceMinimized {
HStack(spacing: 16) {
ForEach(self.days, id: \.id) { day in day }
}
.frame(height: 100)
}
}
.onAppear(perform: self.actionOnAppear)
Expand Down Expand Up @@ -227,12 +212,18 @@ extension WidgetLibrary.UI {
Button {
self.state.session.date = DateHelper.startOfDay(self.date)
} label: {
VStack {
Text(DateHelper.todayShort(self.date, format: "EEE"))
.bold(self.isToday || self.isSelected)
.padding(.top, 8)
.opacity(self.isToday ? 1 : 0.8)
Divider()
VStack(spacing: 0) {
VStack {
HStack {
Spacer()
Text(DateHelper.todayShort(self.date, format: "EEE"))
.bold(self.isToday || self.isSelected)
.padding([.top, .bottom], 4)
.opacity(self.isToday ? 1 : 0.8)
Spacer()
}
}
.background(self.isSelected ? self.state.theme.tint : self.isToday ? .blue : Theme.textBackground)
ZStack {
(self.dayNumber > 0 ? self.bgColour.opacity(0.8) : .clear)
if self.dayNumber > 0 {
Expand All @@ -246,25 +237,25 @@ extension WidgetLibrary.UI {
}
}
}
.mask(
RoundedRectangle(cornerRadius: 5)
.fill(!self.isToday && !self.isSelected ? Theme.cPurple : self.bgColour)
.padding([.leading, .trailing], 8)
)
Text(DateHelper.todayShort(self.date, format: "dd"))
.bold(self.isToday || self.isSelected)
.font(.system(size: 40))
.padding([.top, .bottom])
.font(.system(size: 25))
}
}
}
Divider()
Text(DateHelper.todayShort(self.date, format: "MMMM"))
.bold(self.isToday || self.isSelected)
.padding(.bottom, 8)
.opacity(self.isToday ? 1 : 0.8)
VStack {
HStack {
Spacer()
Text(DateHelper.todayShort(self.date, format: "MMMM"))
.bold(self.isToday || self.isSelected)
.padding([.top, .bottom], 4)
.opacity(self.isToday ? 1 : 0.8)
Spacer()
}
}
.background(self.isSelected ? self.state.theme.tint : self.isToday ? .blue : .clear)
}
.background(self.isSelected ? self.state.theme.tint : self.isToday ? .blue : Theme.textBackground)
.background(Theme.textBackground)
.useDefaultHover({ hover in self.isHighlighted = hover })
}
.help("\(self.colourData.count) Tasks due on \(self.state.session.date.formatted(date: .abbreviated, time: .omitted))")
Expand All @@ -274,14 +265,14 @@ extension WidgetLibrary.UI {
.onAppear(perform: self.actionOnAppear)
.contextMenu {
Button {
self.state.session.date = self.date
self.state.to(.timeline)
self.state.session.date = self.date
} label: {
Text("Show Timeline...")
}
Button {
self.state.session.date = self.date
self.state.to(.today)
self.state.session.date = self.date
} label: {
Text("Show Today...")
}
Expand Down Expand Up @@ -567,14 +558,15 @@ extension WidgetLibrary.UI {
self.isHighlighted ? self.state.theme.tint.opacity(0.8) : Theme.textBackground
HStack {
Spacer()
Image(systemName: self.orientation == .leading ? "chevron.left" : "chevron.right")
Image(systemName: self.orientation == .leading ? "chevron.left.chevron.left.dotted" : "chevron.right.dotted.chevron.right")
.foregroundStyle(self.isHighlighted ? self.state.session.appPage.primaryColour : .white)
Spacer()
}
}
.useDefaultHover({ hover in self.isHighlighted = hover })
}
.buttonStyle(.plain)
.help("\(self.orientation == .leading ? "Previous" : "Next") month")
.frame(width: 32)
}
}
Expand Down
Loading

0 comments on commit 4e19edb

Please sign in to comment.