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>
109 lines
4.0 KiB
Swift
109 lines
4.0 KiB
Swift
import WidgetKit
|
|
import SwiftUI
|
|
|
|
struct RecentBookmarksEntry: TimelineEntry {
|
|
let date: Date
|
|
let bookmarks: [WidgetBookmark]
|
|
|
|
static let placeholder = RecentBookmarksEntry(date: .now, bookmarks: [
|
|
WidgetBookmark(url: "https://example.com/a", title: "The Future of AI in 2025", domain: "example.com", faviconUrl: nil),
|
|
WidgetBookmark(url: "https://news.com/b", title: "Why Swift Concurrency Matters", domain: "news.com", faviconUrl: nil),
|
|
WidgetBookmark(url: "https://blog.dev/c", title: "Building Great iOS Apps", domain: "blog.dev", faviconUrl: nil),
|
|
])
|
|
}
|
|
|
|
struct RecentBookmarksProvider: TimelineProvider {
|
|
func placeholder(in context: Context) -> RecentBookmarksEntry { .placeholder }
|
|
|
|
func getSnapshot(in context: Context, completion: @escaping (RecentBookmarksEntry) -> Void) {
|
|
let bookmarks = WidgetDataStore.loadBookmarks()
|
|
completion(RecentBookmarksEntry(date: .now, bookmarks: Array(bookmarks.prefix(3))))
|
|
}
|
|
|
|
func getTimeline(in context: Context, completion: @escaping (Timeline<RecentBookmarksEntry>) -> Void) {
|
|
let bookmarks = WidgetDataStore.loadBookmarks()
|
|
let entry = RecentBookmarksEntry(date: .now, bookmarks: Array(bookmarks.prefix(3)))
|
|
let next = Calendar.current.date(byAdding: .minute, value: 30, to: .now)!
|
|
completion(Timeline(entries: [entry], policy: .after(next)))
|
|
}
|
|
}
|
|
|
|
struct RecentBookmarksWidgetView: View {
|
|
let entry: RecentBookmarksEntry
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
HStack {
|
|
Text("Recent")
|
|
.font(.system(size: 12, weight: .semibold))
|
|
.foregroundStyle(.secondary)
|
|
Spacer()
|
|
Image(systemName: "bookmark.fill")
|
|
.font(.system(size: 11))
|
|
.foregroundStyle(.blue)
|
|
}
|
|
.padding(.bottom, 8)
|
|
|
|
if entry.bookmarks.isEmpty {
|
|
Spacer()
|
|
Text("Save bookmarks to see them here.")
|
|
.font(.system(size: 13))
|
|
.foregroundStyle(.secondary)
|
|
Spacer()
|
|
} else {
|
|
ForEach(Array(entry.bookmarks.enumerated()), id: \.element.id) { idx, bookmark in
|
|
if idx > 0 { Divider().padding(.vertical, 5) }
|
|
Link(destination: .marksBookmark(bookmark.url)) {
|
|
WidgetBookmarkRow(bookmark: bookmark)
|
|
}
|
|
}
|
|
Spacer(minLength: 0)
|
|
}
|
|
}
|
|
.padding(14)
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
.containerBackground(.fill.tertiary, for: .widget)
|
|
}
|
|
}
|
|
|
|
struct WidgetBookmarkRow: View {
|
|
let bookmark: WidgetBookmark
|
|
|
|
var body: some View {
|
|
HStack(spacing: 8) {
|
|
RoundedRectangle(cornerRadius: 5)
|
|
.fill(Color(.systemGray5))
|
|
.frame(width: 26, height: 26)
|
|
.overlay {
|
|
Image(systemName: "bookmark")
|
|
.font(.system(size: 10, weight: .medium))
|
|
.foregroundStyle(Color(.systemGray2))
|
|
}
|
|
VStack(alignment: .leading, spacing: 1) {
|
|
Text(bookmark.title)
|
|
.font(.system(size: 13, weight: .medium))
|
|
.foregroundStyle(.primary)
|
|
.lineLimit(1)
|
|
Text(bookmark.domain)
|
|
.font(.system(size: 11))
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
Spacer(minLength: 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct RecentBookmarksWidget: Widget {
|
|
var body: some WidgetConfiguration {
|
|
StaticConfiguration(
|
|
kind: "com.magicive.marks.RecentBookmarks",
|
|
provider: RecentBookmarksProvider()
|
|
) { entry in
|
|
RecentBookmarksWidgetView(entry: entry)
|
|
}
|
|
.configurationDisplayName("Recent Bookmarks")
|
|
.description("Your latest saved bookmarks.")
|
|
.supportedFamilies([.systemMedium])
|
|
}
|
|
}
|