Use widget space by family
This commit is contained in:
12
TESTING.md
12
TESTING.md
@@ -235,13 +235,15 @@ answer before uploading it to App Store Connect.
|
|||||||
- [ ] Upgrade over a build with an existing server. Launch Gotcha once, then
|
- [ ] Upgrade over a build with an existing server. Launch Gotcha once, then
|
||||||
add both **Recent Activity** and **Open Pull Requests** from the system
|
add both **Recent Activity** and **Open Pull Requests** from the system
|
||||||
widget gallery; the saved server and Keychain token work without re-entry.
|
widget gallery; the saved server and Keychain token work without re-entry.
|
||||||
- [ ] Both widgets are offered only in the large system size and use native
|
- [ ] Both widgets are offered in medium and large system sizes and use native
|
||||||
widget margins, typography, tint, relative update time, and light/dark
|
widget margins, typography, tint, relative update time, and light/dark
|
||||||
appearances without clipping at the largest accessibility text size.
|
appearances without clipping at the largest accessibility text size.
|
||||||
- [ ] Recent Activity shows up to six latest activity rows for its configured
|
- [ ] In medium size, Recent Activity shows up to three latest activity rows
|
||||||
server. Open Pull Requests shows up to five open pull requests. Loading,
|
and Open Pull Requests shows up to two open pull requests. In large size,
|
||||||
empty, missing-server, and API-error states remain legible and do not
|
they show up to ten and five rows respectively. Confirm both the widget
|
||||||
expose account data while the device is locked.
|
gallery previews and live configured timelines use those family-specific
|
||||||
|
row counts. Loading, empty, missing-server, and API-error states remain
|
||||||
|
legible and do not expose account data while the device is locked.
|
||||||
- [ ] Add a second server profile, long-press each widget, choose **Edit
|
- [ ] Add a second server profile, long-press each widget, choose **Edit
|
||||||
Widget**, and assign a different server to each. The displayed server
|
Widget**, and assign a different server to each. The displayed server
|
||||||
name and rows update independently; deleting a selected server changes
|
name and rows update independently; deleting a selected server changes
|
||||||
|
|||||||
@@ -20,46 +20,23 @@ private struct ActivityProvider: AppIntentTimelineProvider {
|
|||||||
ActivityEntry(
|
ActivityEntry(
|
||||||
date: .now,
|
date: .now,
|
||||||
serverName: "My Server",
|
serverName: "My Server",
|
||||||
rows: [
|
rows: activityPlaceholderRows(for: context.family),
|
||||||
ActivityRow(
|
|
||||||
icon: .push,
|
|
||||||
title: "Pushed to main in owner/project",
|
|
||||||
detail: "",
|
|
||||||
meta: "Just now",
|
|
||||||
target: .commit,
|
|
||||||
owner: "owner",
|
|
||||||
repository: "project",
|
|
||||||
number: 0,
|
|
||||||
sha: ""
|
|
||||||
),
|
|
||||||
ActivityRow(
|
|
||||||
icon: .issue,
|
|
||||||
title: "Opened an issue in owner/project",
|
|
||||||
detail: "",
|
|
||||||
meta: "5m ago",
|
|
||||||
target: .issue,
|
|
||||||
owner: "owner",
|
|
||||||
repository: "project",
|
|
||||||
number: 1,
|
|
||||||
sha: ""
|
|
||||||
),
|
|
||||||
],
|
|
||||||
message: nil
|
message: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshot(for configuration: ActivityWidgetIntent, in context: Context) async -> ActivityEntry {
|
func snapshot(for configuration: ActivityWidgetIntent, in context: Context) async -> ActivityEntry {
|
||||||
context.isPreview ? placeholder(in: context) : await load(configuration)
|
context.isPreview ? placeholder(in: context) : await load(configuration, family: context.family)
|
||||||
}
|
}
|
||||||
|
|
||||||
func timeline(for configuration: ActivityWidgetIntent, in context: Context) async -> Timeline<ActivityEntry> {
|
func timeline(for configuration: ActivityWidgetIntent, in context: Context) async -> Timeline<ActivityEntry> {
|
||||||
Timeline(
|
Timeline(
|
||||||
entries: [await load(configuration)],
|
entries: [await load(configuration, family: context.family)],
|
||||||
policy: .after(Date.now.addingTimeInterval(15 * 60))
|
policy: .after(Date.now.addingTimeInterval(15 * 60))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func load(_ configuration: ActivityWidgetIntent) async -> ActivityEntry {
|
private func load(_ configuration: ActivityWidgetIntent, family: WidgetFamily) async -> ActivityEntry {
|
||||||
guard
|
guard
|
||||||
let selection = configuration.server,
|
let selection = configuration.server,
|
||||||
let server = WidgetEnvironment.server(for: selection)
|
let server = WidgetEnvironment.server(for: selection)
|
||||||
@@ -72,7 +49,8 @@ private struct ActivityProvider: AppIntentTimelineProvider {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
let page = try await WidgetEnvironment.core().widgetActivity(serverId: server.id, limit: 6)
|
let limit = activityLimit(for: family)
|
||||||
|
let page = try await WidgetEnvironment.core().widgetActivity(serverId: server.id, limit: limit)
|
||||||
return ActivityEntry(
|
return ActivityEntry(
|
||||||
date: .now,
|
date: .now,
|
||||||
serverName: page.serverName,
|
serverName: page.serverName,
|
||||||
@@ -95,42 +73,23 @@ private struct PullsProvider: AppIntentTimelineProvider {
|
|||||||
PullsEntry(
|
PullsEntry(
|
||||||
date: .now,
|
date: .now,
|
||||||
serverName: "My Server",
|
serverName: "My Server",
|
||||||
rows: [
|
rows: pullPlaceholderRows(for: context.family),
|
||||||
PullRow(
|
|
||||||
number: 42,
|
|
||||||
owner: "owner",
|
|
||||||
repository: "project",
|
|
||||||
state: .open,
|
|
||||||
title: "project #42\nImprove the activity screen",
|
|
||||||
summary: "",
|
|
||||||
meta: "Open · developer · Just now"
|
|
||||||
),
|
|
||||||
PullRow(
|
|
||||||
number: 41,
|
|
||||||
owner: "owner",
|
|
||||||
repository: "project",
|
|
||||||
state: .open,
|
|
||||||
title: "project #41\nFix repository navigation",
|
|
||||||
summary: "",
|
|
||||||
meta: "Open · developer · 10m ago"
|
|
||||||
),
|
|
||||||
],
|
|
||||||
message: nil
|
message: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshot(for configuration: PullsWidgetIntent, in context: Context) async -> PullsEntry {
|
func snapshot(for configuration: PullsWidgetIntent, in context: Context) async -> PullsEntry {
|
||||||
context.isPreview ? placeholder(in: context) : await load(configuration)
|
context.isPreview ? placeholder(in: context) : await load(configuration, family: context.family)
|
||||||
}
|
}
|
||||||
|
|
||||||
func timeline(for configuration: PullsWidgetIntent, in context: Context) async -> Timeline<PullsEntry> {
|
func timeline(for configuration: PullsWidgetIntent, in context: Context) async -> Timeline<PullsEntry> {
|
||||||
Timeline(
|
Timeline(
|
||||||
entries: [await load(configuration)],
|
entries: [await load(configuration, family: context.family)],
|
||||||
policy: .after(Date.now.addingTimeInterval(15 * 60))
|
policy: .after(Date.now.addingTimeInterval(15 * 60))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func load(_ configuration: PullsWidgetIntent) async -> PullsEntry {
|
private func load(_ configuration: PullsWidgetIntent, family: WidgetFamily) async -> PullsEntry {
|
||||||
guard
|
guard
|
||||||
let selection = configuration.server,
|
let selection = configuration.server,
|
||||||
let server = WidgetEnvironment.server(for: selection)
|
let server = WidgetEnvironment.server(for: selection)
|
||||||
@@ -143,7 +102,8 @@ private struct PullsProvider: AppIntentTimelineProvider {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
let page = try await WidgetEnvironment.core().widgetPulls(serverId: server.id, limit: 5)
|
let limit = pullLimit(for: family)
|
||||||
|
let page = try await WidgetEnvironment.core().widgetPulls(serverId: server.id, limit: limit)
|
||||||
return PullsEntry(
|
return PullsEntry(
|
||||||
date: .now,
|
date: .now,
|
||||||
serverName: page.serverName,
|
serverName: page.serverName,
|
||||||
@@ -161,6 +121,64 @@ private struct PullsProvider: AppIntentTimelineProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func activityLimit(for family: WidgetFamily) -> UInt32 {
|
||||||
|
family == .systemMedium ? 3 : 10
|
||||||
|
}
|
||||||
|
|
||||||
|
private func pullLimit(for family: WidgetFamily) -> UInt32 {
|
||||||
|
family == .systemMedium ? 2 : 5
|
||||||
|
}
|
||||||
|
|
||||||
|
private func activityPlaceholderRows(for family: WidgetFamily) -> [ActivityRow] {
|
||||||
|
let samples: [(ActivityIcon, String, String)] = [
|
||||||
|
(.push, "Pushed to main in owner/project", "Just now"),
|
||||||
|
(.issue, "Opened an issue in owner/project", "5m ago"),
|
||||||
|
(.pullRequest, "Merged a pull request in owner/project", "12m ago"),
|
||||||
|
(.branch, "Created branch feature/widget", "25m ago"),
|
||||||
|
(.tag, "Published tag v1.0.0", "40m ago"),
|
||||||
|
(.release, "Published a release in owner/project", "1h ago"),
|
||||||
|
(.repository, "Created repository owner/new-project", "2h ago"),
|
||||||
|
(.push, "Pushed to release in owner/project", "3h ago"),
|
||||||
|
(.issue, "Closed an issue in owner/project", "4h ago"),
|
||||||
|
(.pullRequest, "Opened a pull request in owner/project", "5h ago"),
|
||||||
|
]
|
||||||
|
return samples.prefix(Int(activityLimit(for: family))).enumerated().map { index, sample in
|
||||||
|
ActivityRow(
|
||||||
|
icon: sample.0,
|
||||||
|
title: sample.1,
|
||||||
|
detail: "",
|
||||||
|
meta: sample.2,
|
||||||
|
target: .commit,
|
||||||
|
owner: "owner",
|
||||||
|
repository: "project",
|
||||||
|
number: Int64(index),
|
||||||
|
sha: ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func pullPlaceholderRows(for family: WidgetFamily) -> [PullRow] {
|
||||||
|
let titles = [
|
||||||
|
"Improve the activity screen",
|
||||||
|
"Fix repository navigation",
|
||||||
|
"Add compact widget layouts",
|
||||||
|
"Refresh server configuration",
|
||||||
|
"Polish issue filters",
|
||||||
|
]
|
||||||
|
return titles.prefix(Int(pullLimit(for: family))).enumerated().map { index, title in
|
||||||
|
let number = Int64(42 - index)
|
||||||
|
return PullRow(
|
||||||
|
number: number,
|
||||||
|
owner: "owner",
|
||||||
|
repository: "project",
|
||||||
|
state: .open,
|
||||||
|
title: "project #\(number)\n\(title)",
|
||||||
|
summary: "",
|
||||||
|
meta: "Open · developer · \(index * 10)m ago"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private struct WidgetHeader: View {
|
private struct WidgetHeader: View {
|
||||||
let title: String
|
let title: String
|
||||||
let symbol: String
|
let symbol: String
|
||||||
@@ -298,7 +316,7 @@ struct ActivityWidget: Widget {
|
|||||||
}
|
}
|
||||||
.configurationDisplayName("Recent Activity")
|
.configurationDisplayName("Recent Activity")
|
||||||
.description("See the latest activity from a configured Gitea or Forgejo server.")
|
.description("See the latest activity from a configured Gitea or Forgejo server.")
|
||||||
.supportedFamilies([.systemLarge])
|
.supportedFamilies([.systemMedium, .systemLarge])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,7 +329,7 @@ struct PullsWidget: Widget {
|
|||||||
}
|
}
|
||||||
.configurationDisplayName("Open Pull Requests")
|
.configurationDisplayName("Open Pull Requests")
|
||||||
.description("See open pull requests from a configured Gitea or Forgejo server.")
|
.description("See open pull requests from a configured Gitea or Forgejo server.")
|
||||||
.supportedFamilies([.systemLarge])
|
.supportedFamilies([.systemMedium, .systemLarge])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user