Complete ground-up rewrite in SwiftUI targeting iOS 26. Drops the Flutter/Dart codebase entirely in favour of a lean native app with no third-party dependencies. Features shipped: - Bookmark list with pagination, pull-to-refresh, swipe delete/archive - Add bookmark form + iOS share extension (zero-tap save from any app) - Tags tab — all tags sorted by count, tap to browse filtered bookmarks - Native search tab (Tab role: .search) with instant client-side filtering - AI enrichment via OpenRouter (google/gemini-2.0-flash-lite-001): auto-summary and tag generation, semantic search, smart collections - Settings: linkding server config, OpenRouter API key - App Groups for credential sharing between main app and share extension - Swift 6 strict concurrency throughout (@Observable, @MainActor) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
67 lines
2.8 KiB
Swift
67 lines
2.8 KiB
Swift
import SwiftUI
|
|
|
|
struct CollectionsView: View {
|
|
@Bindable var viewModel: BookmarksViewModel
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Group {
|
|
if viewModel.isGeneratingCollections {
|
|
VStack(spacing: 16) {
|
|
ProgressView()
|
|
Text("Building smart collections…")
|
|
.font(.system(size: 15))
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
} else if viewModel.smartCollections.isEmpty {
|
|
ContentUnavailableView(
|
|
"No Collections Yet",
|
|
systemImage: "sparkles",
|
|
description: Text("Tap \"Smart Collections\" to group your bookmarks by topic.")
|
|
)
|
|
} else {
|
|
List(viewModel.smartCollections) { collection in
|
|
Section {
|
|
let items = viewModel.bookmarks.filter { collection.bookmarkIds.contains($0.id) }
|
|
ForEach(items) { bookmark in
|
|
BookmarkRow(bookmark: bookmark)
|
|
.listRowInsets(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20))
|
|
}
|
|
} header: {
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(collection.name)
|
|
.font(.system(size: 14, weight: .semibold))
|
|
.foregroundStyle(.primary)
|
|
if !collection.description.isEmpty {
|
|
Text(collection.description)
|
|
.font(.system(size: 12))
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
.padding(.vertical, 4)
|
|
}
|
|
}
|
|
.listStyle(.insetGrouped)
|
|
}
|
|
}
|
|
.navigationTitle("Smart Collections")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarLeading) {
|
|
Button {
|
|
Task { await viewModel.generateSmartCollections() }
|
|
} label: {
|
|
Image(systemName: "arrow.clockwise")
|
|
}
|
|
.disabled(viewModel.isGeneratingCollections)
|
|
}
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Button("Done") { dismiss() }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|