Files
linkding-ios/Marks/Views/CollectionsView.swift
Krishna Kumar b7ef5c2215 Replace Flutter app with native SwiftUI rewrite (iOS 26)
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>
2026-05-21 08:33:01 -05:00

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() }
}
}
}
}
}