- added accent color

- fixed iPad layout with split view
This commit is contained in:
Ilyas Hallak 2025-06-26 20:52:02 +02:00
parent 6ae1b5853e
commit c1eb2109ed
7 changed files with 234 additions and 29 deletions

View File

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
5D2B7FB92DFA27A400EBDB2B /* URLShare.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 5D2B7FAF2DFA27A400EBDB2B /* URLShare.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 5D2B7FB92DFA27A400EBDB2B /* URLShare.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 5D2B7FAF2DFA27A400EBDB2B /* URLShare.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
5D348CC32E0C9F4F00D0AF21 /* netfox in Frameworks */ = {isa = PBXBuildFile; productRef = 5D348CC22E0C9F4F00D0AF21 /* netfox */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -133,6 +134,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
5D348CC32E0C9F4F00D0AF21 /* netfox in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -220,6 +222,7 @@
); );
name = readeck; name = readeck;
packageProductDependencies = ( packageProductDependencies = (
5D348CC22E0C9F4F00D0AF21 /* netfox */,
); );
productName = readeck; productName = readeck;
productReference = 5D45F9C82DF858680048D5B8 /* readeck.app */; productReference = 5D45F9C82DF858680048D5B8 /* readeck.app */;
@ -306,6 +309,9 @@
); );
mainGroup = 5D45F9BF2DF858680048D5B8; mainGroup = 5D45F9BF2DF858680048D5B8;
minimizedProjectReferenceProxies = 1; minimizedProjectReferenceProxies = 1;
packageReferences = (
5D348CC12E0C9F4F00D0AF21 /* XCRemoteSwiftPackageReference "netfox" */,
);
preferredProjectObjectVersion = 77; preferredProjectObjectVersion = 77;
productRefGroup = 5D45F9C92DF858680048D5B8 /* Products */; productRefGroup = 5D45F9C92DF858680048D5B8 /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -790,6 +796,25 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
5D348CC12E0C9F4F00D0AF21 /* XCRemoteSwiftPackageReference "netfox" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/kasketis/netfox";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.21.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
5D348CC22E0C9F4F00D0AF21 /* netfox */ = {
isa = XCSwiftPackageProductDependency;
package = 5D348CC12E0C9F4F00D0AF21 /* XCRemoteSwiftPackageReference "netfox" */;
productName = netfox;
};
/* End XCSwiftPackageProductDependency section */
}; };
rootObject = 5D45F9C02DF858680048D5B8 /* Project object */; rootObject = 5D45F9C02DF858680048D5B8 /* Project object */;
} }

View File

@ -0,0 +1,15 @@
{
"originHash" : "7374154e7686de69a9f88fbafb081b646b02140f8d82770f46fa750840581e0e",
"pins" : [
{
"identity" : "netfox",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kasketis/netfox",
"state" : {
"revision" : "557576032736fd3140422baefb68b8f76c55088f",
"version" : "1.21.0"
}
}
],
"version" : 3
}

View File

@ -3,4 +3,52 @@
uuid = "FAD7B3BD-946C-4129-A614-E1823F18EC12" uuid = "FAD7B3BD-946C-4129-A614-E1823F18EC12"
type = "1" type = "1"
version = "2.0"> version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "B67988B8-09FD-461D-A5FA-7D72A318247D"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "readeck/UI/BookmarkDetail/BookmarkDetailViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "39"
endingLineNumber = "39"
landmarkName = "loadBookmarkDetail(id:)"
landmarkType = "7">
<Locations>
<Location
uuid = "B67988B8-09FD-461D-A5FA-7D72A318247D - 5494d64ddd867e19"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "(6) suspend resume partial function for readeck.BookmarkDetailViewModel.loadBookmarkDetail(id: Swift.String) async -&gt; ()"
moduleName = "readeck.debug.dylib"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/ilyashallak/Privat/Projects/readeck/readeck/UI/BookmarkDetail/BookmarkDetailViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "39"
endingLineNumber = "39">
</Location>
<Location
uuid = "B67988B8-09FD-461D-A5FA-7D72A318247D - ac249dd33728018"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "(7) suspend resume partial function for readeck.BookmarkDetailViewModel.loadBookmarkDetail(id: Swift.String) async -&gt; ()"
moduleName = "readeck.debug.dylib"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/ilyashallak/Privat/Projects/readeck/readeck/UI/BookmarkDetail/BookmarkDetailViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "39"
endingLineNumber = "39">
</Location>
</Locations>
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket> </Bucket>

View File

@ -1,3 +1,5 @@
import Foundation
import Combine
import SwiftUI import SwiftUI
struct BookmarksView: View { struct BookmarksView: View {
@ -6,10 +8,15 @@ struct BookmarksView: View {
@State private var selectedBookmarkId: String? @State private var selectedBookmarkId: String?
let state: BookmarkState let state: BookmarkState
@Binding var selectedBookmark: Bookmark?
@State private var showingAddBookmarkFromShare = false @State private var showingAddBookmarkFromShare = false
@State private var shareURL = "" @State private var shareURL = ""
@State private var shareTitle = "" @State private var shareTitle = ""
@Environment(\.horizontalSizeClass) var horizontalSizeClass
@Environment(\.verticalSizeClass) var verticalSizeClass
var body: some View { var body: some View {
NavigationStack { NavigationStack {
ZStack { ZStack {
@ -19,7 +26,11 @@ struct BookmarksView: View {
List { List {
ForEach(viewModel.bookmarks, id: \.id) { bookmark in ForEach(viewModel.bookmarks, id: \.id) { bookmark in
Button(action: { Button(action: {
selectedBookmarkId = bookmark.id if UIDevice.isPhone {
selectedBookmarkId = bookmark.id
} else {
selectedBookmark = bookmark
}
}) { }) {
BookmarkCardView( BookmarkCardView(
bookmark: bookmark, bookmark: bookmark,
@ -105,13 +116,13 @@ struct BookmarksView: View {
.sheet(isPresented: $showingAddBookmark) { .sheet(isPresented: $showingAddBookmark) {
AddBookmarkView(prefilledURL: shareURL, prefilledTitle: shareTitle) AddBookmarkView(prefilledURL: shareURL, prefilledTitle: shareTitle)
} }
.alert("Fehler", isPresented: .constant(viewModel.errorMessage != nil)) { /*.alert("Fehler", isPresented: .constant(viewModel.errorMessage != nil)) {
Button("OK", role: .cancel) { Button("OK", role: .cancel) {
viewModel.errorMessage = nil viewModel.errorMessage = nil
} }
} message: { } message: {
Text(viewModel.errorMessage ?? "") Text(viewModel.errorMessage ?? "")
} }*/
.task { .task {
await viewModel.loadBookmarks(state: state) await viewModel.loadBookmarks(state: state)
} }
@ -123,24 +134,8 @@ struct BookmarksView: View {
} }
} }
} }
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("AddBookmarkFromShare"))) { notification in
handleShareNotification(notification)
}
} }
} .searchable(text: $viewModel.searchQuery, placement: .automatic, prompt: "Search...")
private func handleShareNotification(_ notification: Notification) {
guard let userInfo = notification.userInfo,
let url = userInfo["url"] as? String,
!url.isEmpty else {
return
}
shareURL = url
shareTitle = userInfo["title"] as? String ?? ""
showingAddBookmark = true
print("Received share notification - URL: \(url), Title: \(shareTitle)")
} }
} }

View File

@ -24,6 +24,14 @@ class BookmarksViewModel {
private var offset = 0 private var offset = 0
private var hasMoreData = true private var hasMoreData = true
var searchQuery: String = "" {
didSet {
throttleSearch()
}
}
private var searchWorkItem: DispatchWorkItem?
init() { init() {
setupNotificationObserver() setupNotificationObserver()
} }
@ -53,6 +61,20 @@ class BookmarksViewModel {
print("Received share notification - URL: \(url)") print("Received share notification - URL: \(url)")
} }
private func throttleSearch() {
searchWorkItem?.cancel()
let workItem = DispatchWorkItem { [weak self] in
guard let self = self else { return }
Task {
await self.loadBookmarks(state: self.currentState)
}
}
searchWorkItem = workItem
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: workItem)
}
@MainActor @MainActor
func loadBookmarks(state: BookmarkState = .unread) async { func loadBookmarks(state: BookmarkState = .unread) async {
isLoading = true isLoading = true
@ -62,7 +84,12 @@ class BookmarksViewModel {
hasMoreData = true // Pagination zurücksetzen hasMoreData = true // Pagination zurücksetzen
do { do {
let newBookmarks = try await getBooksmarksUseCase.execute(state: state, limit: limit, offset: offset) let newBookmarks = try await getBooksmarksUseCase.execute(
state: state,
limit: limit,
offset: offset,
search: searchQuery // Suche integrieren
)
bookmarks = newBookmarks bookmarks = newBookmarks
hasMoreData = newBookmarks.count == limit // Prüfen, ob weitere Daten verfügbar sind hasMoreData = newBookmarks.count == limit // Prüfen, ob weitere Daten verfügbar sind
} catch { } catch {

View File

@ -1,7 +1,7 @@
import SwiftUI import SwiftUI
import Foundation import Foundation
enum BookmarkState: String, CaseIterable { enum BookmarkState: String, CaseIterable {
case unread = "unread" case unread = "unread"
case favorite = "favorite" case favorite = "favorite"
case archived = "archived" case archived = "archived"
@ -31,22 +31,40 @@ enum BookmarkState: String, CaseIterable {
struct MainTabView: View { struct MainTabView: View {
@State private var selectedTab: String = "Ungelesen" @State private var selectedTab: String = "Ungelesen"
// sizeClass
@Environment(\.horizontalSizeClass)
var horizontalSizeClass
@Environment(\.verticalSizeClass)
var verticalSizeClass
@State var selectedBookmark: Bookmark?
var body: some View { var body: some View {
if UIDevice.isPhone {
PhoneView()
} else {
PadView()
}
}
@ViewBuilder
private func PhoneView() -> some View {
TabView(selection: $selectedTab) { TabView(selection: $selectedTab) {
BookmarksView(state: .unread) BookmarksView(state: .unread, selectedBookmark: .constant(nil))
.tabItem { .tabItem {
Label("Ungelesen", systemImage: "house") Label("Ungelesen", systemImage: "house")
} }
.tag("Ungelesen") .tag("Ungelesen")
BookmarksView(state: .favorite) BookmarksView(state: .favorite, selectedBookmark: .constant(nil))
.tabItem { .tabItem {
Label("Favoriten", systemImage: "heart") Label("Favoriten", systemImage: "heart")
} }
.tag("Favoriten") .tag("Favoriten")
BookmarksView(state: .archived) BookmarksView(state: .archived, selectedBookmark: .constant(nil))
.tabItem { .tabItem {
Label("Archiv", systemImage: "archivebox") Label("Archiv", systemImage: "archivebox")
} }
@ -56,12 +74,83 @@ struct MainTabView: View {
.tabItem { .tabItem {
Label("Settings", systemImage: "gear") Label("Settings", systemImage: "gear")
} }
.tag("Settings") .tag("Settings")
}
.accentColor(.accentColor)
}
@ViewBuilder
private func PadView() -> some View {
TabView(selection: $selectedTab) {
// Ungelesen Tab
NavigationSplitView {
BookmarksView(state: .unread, selectedBookmark: $selectedBookmark)
} detail: {
if let selectedBookmark = selectedBookmark {
BookmarkDetailView(bookmarkId: selectedBookmark.id)
} else {
Text("Select a bookmark")
.foregroundColor(.gray)
}
}
.tabItem {
Label("Unread", systemImage: "house")
}
.tag("Unread")
NavigationSplitViewContainer(state: .favorite, selectedBookmark: $selectedBookmark)
.tabItem {
Label("Favoriten", systemImage: "heart")
}
.tag("Favorite")
NavigationSplitViewContainer(state: .archived, selectedBookmark: $selectedBookmark)
.tabItem {
Label("Archive", systemImage: "archivebox")
}
.tag("Archive")
SettingsView()
.tabItem {
Label("Settings", systemImage: "gear")
}
.tag("Settings")
}
.accentColor(.accentColor)
}
}
// Container für NavigationSplitView
struct NavigationSplitViewContainer: View {
let state: BookmarkState
@Binding var selectedBookmark: Bookmark?
var body: some View {
NavigationSplitView {
BookmarksView(state: state, selectedBookmark: $selectedBookmark)
} detail: {
if let selectedBookmark = selectedBookmark {
BookmarkDetailView(bookmarkId: selectedBookmark.id)
} else {
Text("Select a bookmark")
.foregroundColor(.gray)
}
} }
.accentColor(.blue)
} }
} }
#Preview { #Preview {
MainTabView() MainTabView()
} }
extension UIDevice {
static var isPad: Bool {
return UIDevice.current.userInterfaceIdiom == .pad
}
static var isPhone: Bool {
return UIDevice.current.userInterfaceIdiom == .phone
}
}

View File

@ -6,6 +6,7 @@
// //
import SwiftUI import SwiftUI
import netfox
@main @main
struct readeckApp: App { struct readeckApp: App {
@ -18,6 +19,11 @@ struct readeckApp: App {
.onOpenURL { url in .onOpenURL { url in
handleIncomingURL(url) handleIncomingURL(url)
} }
.onAppear {
#if DEBUG
NFX.sharedInstance().start()
#endif
}
} }
} }