diff --git a/TESTING.md b/TESTING.md index 3c6aa3f..3519a0a 100644 --- a/TESTING.md +++ b/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 add both **Recent Activity** and **Open Pull Requests** from the system 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 appearances without clipping at the largest accessibility text size. -- [ ] Recent Activity shows up to six latest activity rows for its configured - server. Open Pull Requests shows up to five open pull requests. Loading, - empty, missing-server, and API-error states remain legible and do not - expose account data while the device is locked. +- [ ] In medium size, Recent Activity shows up to three latest activity rows + and Open Pull Requests shows up to two open pull requests. In large size, + they show up to ten and five rows respectively. Confirm both the widget + 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 Widget**, and assign a different server to each. The displayed server name and rows update independently; deleting a selected server changes diff --git a/ios/Widgets/GotchaWidgets.swift b/ios/Widgets/GotchaWidgets.swift index fef0bb2..a4f40ec 100644 --- a/ios/Widgets/GotchaWidgets.swift +++ b/ios/Widgets/GotchaWidgets.swift @@ -20,46 +20,23 @@ private struct ActivityProvider: AppIntentTimelineProvider { ActivityEntry( date: .now, serverName: "My Server", - rows: [ - 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: "" - ), - ], + rows: activityPlaceholderRows(for: context.family), message: nil ) } 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 { Timeline( - entries: [await load(configuration)], + entries: [await load(configuration, family: context.family)], policy: .after(Date.now.addingTimeInterval(15 * 60)) ) } - private func load(_ configuration: ActivityWidgetIntent) async -> ActivityEntry { + private func load(_ configuration: ActivityWidgetIntent, family: WidgetFamily) async -> ActivityEntry { guard let selection = configuration.server, let server = WidgetEnvironment.server(for: selection) @@ -72,7 +49,8 @@ private struct ActivityProvider: AppIntentTimelineProvider { ) } 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( date: .now, serverName: page.serverName, @@ -95,42 +73,23 @@ private struct PullsProvider: AppIntentTimelineProvider { PullsEntry( date: .now, serverName: "My Server", - rows: [ - 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" - ), - ], + rows: pullPlaceholderRows(for: context.family), message: nil ) } 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 { Timeline( - entries: [await load(configuration)], + entries: [await load(configuration, family: context.family)], policy: .after(Date.now.addingTimeInterval(15 * 60)) ) } - private func load(_ configuration: PullsWidgetIntent) async -> PullsEntry { + private func load(_ configuration: PullsWidgetIntent, family: WidgetFamily) async -> PullsEntry { guard let selection = configuration.server, let server = WidgetEnvironment.server(for: selection) @@ -143,7 +102,8 @@ private struct PullsProvider: AppIntentTimelineProvider { ) } 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( date: .now, 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 { let title: String let symbol: String @@ -298,7 +316,7 @@ struct ActivityWidget: Widget { } .configurationDisplayName("Recent Activity") .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") .description("See open pull requests from a configured Gitea or Forgejo server.") - .supportedFamilies([.systemLarge]) + .supportedFamilies([.systemMedium, .systemLarge]) } }