Fix ShareExtension "Open Marks first" + add widget + BookmarkListRow polish
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>
This commit is contained in:
Krishna Kumar
2026-05-24 13:11:05 -05:00
parent d87500a7dc
commit 199fc2d043
23 changed files with 1437 additions and 172 deletions

View File

@@ -0,0 +1,98 @@
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])
}
}