-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTodayTodoWidgetEntryView.swift
More file actions
152 lines (134 loc) · 4.57 KB
/
Copy pathTodayTodoWidgetEntryView.swift
File metadata and controls
152 lines (134 loc) · 4.57 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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//
// TodayTodoWidgetEntryView.swift
// DevLogWidget
//
// Created by opfic on 4/15/26.
//
import SwiftUI
import WidgetKit
struct TodayTodoWidgetEntryView: View {
let entry: TodayTodoWidgetEntry
@Environment(\.widgetFamily) private var widgetFamily
var body: some View {
VStack(alignment: .leading) {
Text("Today")
.font(.headline)
Spacer()
if let snapshot = entry.snapshot {
content(snapshot)
} else {
emptyState
}
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}
@ViewBuilder
private func content(_ snapshot: TodayWidgetSnapshot) -> some View {
switch widgetFamily {
case .systemSmall:
VStack(alignment: .leading, spacing: 4) {
Text("\(snapshot.totalCount)")
.font(.system(size: 28, weight: .bold))
if let item = displayedItems(from: snapshot).first {
todoRow(item)
} else {
Text("widget_today_empty_message")
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
}
}
case .systemMedium:
let items = displayedItems(from: snapshot)
VStack(alignment: .leading, spacing: 6) {
if items.isEmpty {
Text("widget_today_empty_message")
.multilineTextAlignment(.center)
.font(.caption)
.foregroundStyle(.secondary)
} else {
ForEach(items, id: \.id) { item in
todoRow(item, lineLimit: 1)
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
default:
EmptyView()
}
}
@ViewBuilder
private var emptyState: some View {
switch widgetFamily {
case .systemSmall:
GeometryReader { proxy in
VStack(alignment: .leading, spacing: 4) {
placeholderTodoCount()
placeholderTodoRow(width: placeholderTodoRowWidth(in: proxy.size.width, at: 0))
}
}
.frame(height: 56)
.frame(maxWidth: .infinity, alignment: .leading)
case .systemMedium:
GeometryReader { proxy in
VStack(alignment: .leading, spacing: 6) {
ForEach(0..<3, id: \.self) { index in
placeholderTodoRow(width: placeholderTodoRowWidth(in: proxy.size.width, at: index))
}
}
}
.frame(height: 56)
.frame(maxWidth: .infinity, alignment: .leading)
default:
EmptyView()
}
}
private func displayedItems(from snapshot: TodayWidgetSnapshot) -> [WidgetTodoSnapshotItem] {
Array(snapshot
.sections
.flatMap(\.items)
.prefix(3))
}
private func todoRow(_ item: WidgetTodoSnapshotItem, lineLimit: Int? = nil) -> some View {
HStack(spacing: 6) {
Text("#\(item.number)")
.font(.caption2)
.foregroundStyle(.secondary)
if item.isPinned {
Image(systemName: "star.fill")
.font(.caption2)
.foregroundStyle(.orange)
}
Text(item.title)
.font(.caption)
.lineLimit(lineLimit)
}
}
private func placeholderTodoCount() -> some View {
RoundedRectangle(cornerRadius: 4)
.fill(Color.secondary.opacity(0.18))
.frame(width: 22, height: 28)
}
private func placeholderTodoRow(width: CGFloat) -> some View {
HStack(spacing: 6) {
RoundedRectangle(cornerRadius: 3)
.fill(Color.secondary.opacity(0.18))
.frame(width: 22, height: 8)
RoundedRectangle(cornerRadius: 3)
.fill(Color.secondary.opacity(0.18))
.frame(width: width, height: 8)
}
}
private func placeholderTodoRowWidth(in availableWidth: CGFloat, at index: Int) -> CGFloat {
let titleAreaWidth = max(availableWidth - 28, 0)
switch index {
case 0:
return titleAreaWidth * 2 / 3
case 1:
return titleAreaWidth / 2
default:
return titleAreaWidth * 3 / 5
}
}
}