- Add cache settings UseCases (get/update size, clear cache) - Create CacheSettingsViewModel and OfflineSettingsViewModel - Replace direct UserDefaults access with repository pattern - Add CachedArticlesPreviewView for viewing offline articles - Integrate offline settings into main SettingsContainerView - Wire up new UseCases in factory pattern
201 lines
6.3 KiB
Swift
201 lines
6.3 KiB
Swift
//
|
|
// CachedArticlesPreviewView.swift
|
|
// readeck
|
|
//
|
|
// Created by Claude on 30.11.25.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct CachedArticlesPreviewView: View {
|
|
|
|
// MARK: - State
|
|
|
|
@State private var viewModel = CachedArticlesPreviewViewModel()
|
|
@State private var selectedBookmarkId: String?
|
|
@EnvironmentObject var appSettings: AppSettings
|
|
|
|
// MARK: - Body
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
if viewModel.isLoading && viewModel.cachedBookmarks.isEmpty {
|
|
loadingView
|
|
} else if let errorMessage = viewModel.errorMessage {
|
|
errorView(message: errorMessage)
|
|
} else if viewModel.cachedBookmarks.isEmpty {
|
|
emptyStateView
|
|
} else {
|
|
cachedBookmarksList
|
|
}
|
|
}
|
|
.navigationTitle("Cached Articles")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.navigationDestination(
|
|
item: Binding<String?>(
|
|
get: { selectedBookmarkId },
|
|
set: { selectedBookmarkId = $0 }
|
|
)
|
|
) { bookmarkId in
|
|
BookmarkDetailView(bookmarkId: bookmarkId)
|
|
.toolbar(.hidden, for: .tabBar)
|
|
}
|
|
.task {
|
|
await viewModel.loadCachedBookmarks()
|
|
}
|
|
}
|
|
|
|
// MARK: - View Components
|
|
|
|
@ViewBuilder
|
|
private var cachedBookmarksList: some View {
|
|
List {
|
|
Section {
|
|
ForEach(viewModel.cachedBookmarks, id: \.id) { bookmark in
|
|
Button(action: {
|
|
selectedBookmarkId = bookmark.id
|
|
}) {
|
|
BookmarkCardView(
|
|
bookmark: bookmark,
|
|
currentState: .unread,
|
|
layout: .magazine,
|
|
onArchive: { _ in },
|
|
onDelete: { _ in },
|
|
onToggleFavorite: { _ in }
|
|
)
|
|
}
|
|
.buttonStyle(PlainButtonStyle())
|
|
.listRowInsets(EdgeInsets(
|
|
top: 12,
|
|
leading: 16,
|
|
bottom: 12,
|
|
trailing: 16
|
|
))
|
|
.listRowSeparator(.hidden)
|
|
.listRowBackground(Color(R.color.bookmark_list_bg))
|
|
}
|
|
} header: {
|
|
HStack {
|
|
Image(systemName: "checkmark.circle.fill")
|
|
.foregroundColor(.green)
|
|
.font(.caption)
|
|
Text("\(viewModel.cachedBookmarks.count) articles cached")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
.textCase(nil)
|
|
.padding(.bottom, 4)
|
|
} footer: {
|
|
Text("These articles are available offline. You can read them without an internet connection.")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
.listStyle(.insetGrouped)
|
|
.background(Color(R.color.bookmark_list_bg))
|
|
.scrollContentBackground(.hidden)
|
|
.refreshable {
|
|
await viewModel.refreshList()
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var loadingView: some View {
|
|
VStack(spacing: 16) {
|
|
ProgressView()
|
|
.scaleEffect(1.3)
|
|
.tint(.accentColor)
|
|
|
|
VStack(spacing: 8) {
|
|
Text("Loading Cached Articles")
|
|
.font(.headline)
|
|
.foregroundColor(.primary)
|
|
|
|
Text("Please wait...")
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
.background(Color(R.color.bookmark_list_bg))
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func errorView(message: String) -> some View {
|
|
VStack(spacing: 16) {
|
|
Image(systemName: "exclamationmark.triangle.fill")
|
|
.font(.system(size: 48))
|
|
.foregroundColor(.orange)
|
|
|
|
VStack(spacing: 8) {
|
|
Text("Unable to load cached articles")
|
|
.font(.headline)
|
|
.foregroundColor(.primary)
|
|
|
|
Text(message)
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
.multilineTextAlignment(.center)
|
|
}
|
|
|
|
Button("Try Again") {
|
|
Task {
|
|
await viewModel.loadCachedBookmarks()
|
|
}
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.controlSize(.large)
|
|
}
|
|
.padding(.horizontal, 40)
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
.background(Color(R.color.bookmark_list_bg))
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var emptyStateView: some View {
|
|
VStack(spacing: 20) {
|
|
Image(systemName: "tray")
|
|
.font(.system(size: 64))
|
|
.foregroundColor(.secondary.opacity(0.5))
|
|
|
|
VStack(spacing: 8) {
|
|
Text("No Cached Articles")
|
|
.font(.title2)
|
|
.fontWeight(.semibold)
|
|
.foregroundColor(.primary)
|
|
|
|
Text("Enable offline reading and sync to cache articles for offline access")
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
.multilineTextAlignment(.center)
|
|
.padding(.horizontal, 32)
|
|
}
|
|
|
|
// Hint
|
|
VStack(spacing: 8) {
|
|
HStack(spacing: 8) {
|
|
Image(systemName: "arrow.clockwise")
|
|
.font(.caption)
|
|
Text("Use 'Sync Now' to download articles")
|
|
.font(.caption)
|
|
}
|
|
.foregroundColor(.accentColor)
|
|
.padding(.horizontal, 16)
|
|
.padding(.vertical, 8)
|
|
.background(Color.accentColor.opacity(0.1))
|
|
.clipShape(Capsule())
|
|
}
|
|
.padding(.top, 8)
|
|
}
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
.background(Color(R.color.bookmark_list_bg))
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
NavigationStack {
|
|
CachedArticlesPreviewView()
|
|
.environmentObject(AppSettings())
|
|
}
|
|
}
|