-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTodayFeature+State.swift
More file actions
129 lines (115 loc) · 3.88 KB
/
Copy pathTodayFeature+State.swift
File metadata and controls
129 lines (115 loc) · 3.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//
// TodayFeature+State.swift
// DevLogPresentation
//
// Created by opfic on 6/14/26.
//
import DevLogCore
import Foundation
extension TodayFeature {
static func summaryValue(
for scope: SectionScope,
todos: [TodayTodoItem],
displayOptions: TodayDisplayOptions,
now: Date
) -> Int {
let displayedTodos = displayedTodos(
todos: todos,
displayOptions: displayOptions
)
switch scope {
case .all:
return displayedTodos.count
case .focused:
return displayedTodos.filter(\.isPinned).count
case .overdue:
return displayedTodos.filter { isOverdue($0, now: now) }.count
case .dueSoon:
return displayedTodos.filter { isDueSoon($0, now: now) }.count
}
}
static func displayedTodos(
todos: [TodayTodoItem],
displayOptions: TodayDisplayOptions
) -> [TodayTodoItem] {
let dueDateFilteredTodos: [TodayTodoItem]
switch displayOptions.dueDateVisibility {
case .all:
dueDateFilteredTodos = todos
case .withDueDateOnly:
dueDateFilteredTodos = todos.filter { $0.dueDate != nil }
case .withoutDueDateOnly:
dueDateFilteredTodos = todos.filter { $0.dueDate == nil }
}
switch displayOptions.focusVisibility {
case .all:
return dueDateFilteredTodos
case .focusedOnly:
return dueDateFilteredTodos.filter(\.isPinned)
}
}
static func groupedSectionItems(
from items: [TodayTodoItem],
now: Date
) -> TodayFeature.SectionCollection {
let calendar = Calendar.current
let startOfToday = calendar.startOfDay(for: now)
guard let windowEnd = calendar.date(
byAdding: .day,
value: TodayFeature.upcomingWindowDays,
to: startOfToday
) else {
return TodayFeature.SectionCollection(
focused: items.filter(\.isPinned),
unscheduled: items.filter { !$0.isPinned && $0.dueDate == nil }
)
}
var collection = TodayFeature.SectionCollection()
for item in items {
if item.isPinned {
collection.focused.append(item)
continue
}
guard let dueDate = item.dueDate else {
collection.unscheduled.append(item)
continue
}
let dueDay = calendar.startOfDay(for: dueDate)
if dueDay < startOfToday {
collection.overdue.append(item)
} else if dueDay <= windowEnd {
collection.dueSoon.append(item)
} else {
collection.later.append(item)
}
}
return collection
}
static func isOverdue(_ item: TodayTodoItem, now: Date) -> Bool {
guard let dueDate = item.dueDate else { return false }
let calendar = Calendar.current
return calendar.startOfDay(for: dueDate) < calendar.startOfDay(for: now)
}
static func isDueSoon(_ item: TodayTodoItem, now: Date) -> Bool {
guard let dueDate = item.dueDate else { return false }
let calendar = Calendar.current
let startOfToday = calendar.startOfDay(for: now)
guard let windowEnd = calendar.date(
byAdding: .day,
value: TodayFeature.upcomingWindowDays,
to: startOfToday
) else {
return false
}
let dueDay = calendar.startOfDay(for: dueDate)
return startOfToday <= dueDay && dueDay <= windowEnd
}
static func makeSection(
category: SectionCategory,
title: String,
items: [TodayTodoItem]
) -> [SectionContent] {
guard !items.isEmpty else { return [] }
return [SectionContent(category: category, title: title, items: items)]
}
}