Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Week at a glance" widget, Timeline, and Search UI/UX improvements #334

Merged
merged 10 commits into from
Nov 11, 2024
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