- added accent color
- fixed iPad layout with split view
This commit is contained in:
parent
6ae1b5853e
commit
c1eb2109ed
@ -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 */;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
@ -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 -> ()"
|
||||||
|
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 -> ()"
|
||||||
|
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>
|
||||||
|
|||||||
@ -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)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user