All checks were successful
CI / build-and-deploy (push) Successful in 41s
- Save serverConfig to App Group on every app launch so ShareExtension can always find credentials without requiring a disconnect/reconnect flow - Add MarksWidget target with RandomBookmarkWidget (random saved bookmark, reloads hourly) using file-based App Group storage to avoid CFPreferences issues - Extract BookmarkListRow as shared component with editorial typography tweaks (17pt semibold title, 13pt domain, relative date, 32×32 favicon, italic AI summary, 20pt podcast icon, 16pt horizontal insets) - Add MarksWidget and ShareExtension Xcode schemes for direct run/debug Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
99 lines
3.9 KiB
Swift
99 lines
3.9 KiB
Swift
import WidgetKit
|
|
import SwiftUI
|
|
|
|
struct RandomBookmarkEntry: TimelineEntry {
|
|
let date: Date
|
|
let bookmark: WidgetBookmark?
|
|
|
|
static let placeholder = RandomBookmarkEntry(
|
|
date: .now,
|
|
bookmark: WidgetBookmark(url: "https://example.com", title: "A Saved Article Worth Revisiting", domain: "example.com", faviconUrl: nil)
|
|
)
|
|
}
|
|
|
|
struct RandomBookmarkProvider: TimelineProvider {
|
|
func placeholder(in context: Context) -> RandomBookmarkEntry { .placeholder }
|
|
|
|
func getSnapshot(in context: Context, completion: @escaping (RandomBookmarkEntry) -> Void) {
|
|
let bookmarks = WidgetDataStore.loadBookmarks()
|
|
completion(RandomBookmarkEntry(date: .now, bookmark: bookmarks.randomElement()))
|
|
}
|
|
|
|
func getTimeline(in context: Context, completion: @escaping (Timeline<RandomBookmarkEntry>) -> Void) {
|
|
let bookmarks = WidgetDataStore.loadBookmarks()
|
|
let entry = RandomBookmarkEntry(date: .now, bookmark: bookmarks.randomElement())
|
|
let next = Calendar.current.date(byAdding: .hour, value: 1, to: .now)!
|
|
completion(Timeline(entries: [entry], policy: .after(next)))
|
|
}
|
|
}
|
|
|
|
struct RandomBookmarkWidgetView: View {
|
|
let entry: RandomBookmarkEntry
|
|
|
|
var body: some View {
|
|
Group {
|
|
if let bookmark = entry.bookmark {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
HStack {
|
|
Text("Rediscover")
|
|
.font(.system(size: 12, weight: .semibold))
|
|
.foregroundStyle(.secondary)
|
|
Spacer()
|
|
Image(systemName: "sparkles")
|
|
.font(.system(size: 11))
|
|
.foregroundStyle(.blue)
|
|
}
|
|
Spacer()
|
|
RoundedRectangle(cornerRadius: 7)
|
|
.fill(Color.blue.opacity(0.1))
|
|
.frame(width: 38, height: 38)
|
|
.overlay {
|
|
Image(systemName: "bookmark.fill")
|
|
.font(.system(size: 15))
|
|
.foregroundStyle(.blue)
|
|
}
|
|
Spacer().frame(height: 10)
|
|
Text(bookmark.title)
|
|
.font(.system(size: 14, weight: .semibold))
|
|
.foregroundStyle(.primary)
|
|
.lineLimit(3)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
Text(bookmark.domain)
|
|
.font(.system(size: 12))
|
|
.foregroundStyle(.secondary)
|
|
.padding(.top, 2)
|
|
}
|
|
.padding(14)
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
|
|
.widgetURL(.marksBookmark(bookmark.url))
|
|
} else {
|
|
VStack(spacing: 8) {
|
|
Image(systemName: "bookmark")
|
|
.font(.system(size: 28))
|
|
.foregroundStyle(.secondary)
|
|
Text("Open Marks to\nload bookmarks.")
|
|
.font(.system(size: 12))
|
|
.foregroundStyle(.secondary)
|
|
.multilineTextAlignment(.center)
|
|
}
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
}
|
|
}
|
|
.containerBackground(.fill.tertiary, for: .widget)
|
|
}
|
|
}
|
|
|
|
struct RandomBookmarkWidget: Widget {
|
|
var body: some WidgetConfiguration {
|
|
StaticConfiguration(
|
|
kind: "com.magicive.marks.RandomBookmark",
|
|
provider: RandomBookmarkProvider()
|
|
) { entry in
|
|
RandomBookmarkWidgetView(entry: entry)
|
|
}
|
|
.configurationDisplayName("Rediscover")
|
|
.description("A random saved bookmark to revisit.")
|
|
.supportedFamilies([.systemSmall])
|
|
}
|
|
}
|