feat: Enhance search UX and improve localization
- Replace German strings with English translations in Localizable.xcstrings - Add smooth zoom transitions between bookmark list and detail views using matchedGeometryEffect - Improve search interface with better styling, focus management, and loading states - Enhance bookmark card interactions and visual consistency - Refactor search functionality for cleaner code structure
This commit is contained in:
parent
3981f086f9
commit
b71fc0a4e0
@ -160,12 +160,6 @@
|
||||
},
|
||||
"Jump to last read position (%lld%%)" : {
|
||||
|
||||
},
|
||||
"Keine Bookmarks gefunden." : {
|
||||
|
||||
},
|
||||
"Keine Ergebnisse" : {
|
||||
|
||||
},
|
||||
"Key" : {
|
||||
"extractionState" : "manual"
|
||||
@ -202,6 +196,12 @@
|
||||
},
|
||||
"No bookmarks found in %@." : {
|
||||
|
||||
},
|
||||
"No bookmarks found." : {
|
||||
|
||||
},
|
||||
"No results" : {
|
||||
|
||||
},
|
||||
"OK" : {
|
||||
|
||||
@ -278,12 +278,21 @@
|
||||
},
|
||||
"Saving..." : {
|
||||
|
||||
},
|
||||
"Search" : {
|
||||
|
||||
},
|
||||
"Search or add new tag..." : {
|
||||
|
||||
},
|
||||
"Search results" : {
|
||||
|
||||
},
|
||||
"Search..." : {
|
||||
|
||||
},
|
||||
"Searching..." : {
|
||||
|
||||
},
|
||||
"Select a bookmark or tag" : {
|
||||
|
||||
@ -302,15 +311,6 @@
|
||||
},
|
||||
"Successfully logged in" : {
|
||||
|
||||
},
|
||||
"Suchbegriff eingeben..." : {
|
||||
|
||||
},
|
||||
"Suche" : {
|
||||
|
||||
},
|
||||
"Suche..." : {
|
||||
|
||||
},
|
||||
"Sync interval" : {
|
||||
|
||||
|
||||
@ -342,11 +342,13 @@ class API: PAPI {
|
||||
endpoint: endpoint,
|
||||
responseType: [BookmarkDto].self
|
||||
)
|
||||
|
||||
let currentPage = response.value(forHTTPHeaderField: "Current-Page").flatMap { Int($0) }
|
||||
let totalCount = response.value(forHTTPHeaderField: "Total-Count").flatMap { Int($0) }
|
||||
let totalPages = response.value(forHTTPHeaderField: "Total-Pages").flatMap { Int($0) }
|
||||
let linksHeader = response.value(forHTTPHeaderField: "Link")
|
||||
let links = linksHeader?.components(separatedBy: ",")
|
||||
|
||||
return BookmarksPageDto(
|
||||
bookmarks: bookmarks,
|
||||
currentPage: currentPage,
|
||||
|
||||
@ -78,7 +78,6 @@ class BookmarksRepository: PBookmarksRepository {
|
||||
}
|
||||
|
||||
func searchBookmarks(search: String) async throws -> BookmarksPage {
|
||||
let bookmarkDtos = try await api.searchBookmarks(search: search)
|
||||
return bookmarkDtos.toDomain()
|
||||
try await api.searchBookmarks(search: search).toDomain()
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import Combine
|
||||
|
||||
struct BookmarkDetailView: View {
|
||||
let bookmarkId: String
|
||||
let namespace: Namespace.ID?
|
||||
|
||||
// MARK: - States
|
||||
|
||||
@ -25,8 +26,9 @@ struct BookmarkDetailView: View {
|
||||
|
||||
private let headerHeight: CGFloat = 320
|
||||
|
||||
init(bookmarkId: String, viewModel: BookmarkDetailViewModel = BookmarkDetailViewModel(), webViewHeight: CGFloat = 300, showingFontSettings: Bool = false, showingLabelsSheet: Bool = false, playerUIState: PlayerUIState = .init()) {
|
||||
init(bookmarkId: String, namespace: Namespace.ID? = nil, viewModel: BookmarkDetailViewModel = BookmarkDetailViewModel(), webViewHeight: CGFloat = 300, showingFontSettings: Bool = false, showingLabelsSheet: Bool = false, playerUIState: PlayerUIState = .init()) {
|
||||
self.bookmarkId = bookmarkId
|
||||
self.namespace = namespace
|
||||
self.viewModel = viewModel
|
||||
self.webViewHeight = webViewHeight
|
||||
self.showingFontSettings = showingFontSettings
|
||||
@ -196,10 +198,16 @@ struct BookmarkDetailView: View {
|
||||
.frame(width: geometry.size.width, height: headerHeight + (offset > 0 ? offset : 0))
|
||||
.clipped()
|
||||
.offset(y: (offset > 0 ? -offset : 0))
|
||||
.if(namespace != nil) { view in
|
||||
view.matchedGeometryEffect(id: "image-\(viewModel.bookmarkDetail.id)", in: namespace!)
|
||||
}
|
||||
} placeholder: {
|
||||
Rectangle()
|
||||
.fill(Color.gray.opacity(0.4))
|
||||
.frame(width: geometry.size.width, height: headerHeight)
|
||||
.if(namespace != nil) { view in
|
||||
view.matchedGeometryEffect(id: "image-\(viewModel.bookmarkDetail.id)", in: namespace!)
|
||||
}
|
||||
}
|
||||
// Gradient overlay für bessere Button-Sichtbarkeit
|
||||
LinearGradient(
|
||||
|
||||
@ -1,6 +1,16 @@
|
||||
import SwiftUI
|
||||
import SafariServices
|
||||
|
||||
extension View {
|
||||
@ViewBuilder func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
|
||||
if condition {
|
||||
transform(self)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BookmarkCardView: View {
|
||||
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@ -10,6 +20,7 @@ struct BookmarkCardView: View {
|
||||
let onArchive: (Bookmark) -> Void
|
||||
let onDelete: (Bookmark) -> Void
|
||||
let onToggleFavorite: (Bookmark) -> Void
|
||||
let namespace: Namespace.ID?
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
@ -27,6 +38,9 @@ struct BookmarkCardView: View {
|
||||
.frame(height: 120)
|
||||
}
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
.if(namespace != nil) { view in
|
||||
view.matchedGeometryEffect(id: "image-\(bookmark.id)", in: namespace!)
|
||||
}
|
||||
|
||||
if bookmark.readProgress > 0 && bookmark.isArchived == false && bookmark.isMarked == false {
|
||||
ZStack {
|
||||
@ -223,12 +237,3 @@ struct IconBadge: View {
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
BookmarkCardView(bookmark: .mock, currentState: .all) { _ in
|
||||
|
||||
} onDelete: { _ in
|
||||
|
||||
} onToggleFavorite: { _ in
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@ import SwiftUI
|
||||
|
||||
struct BookmarksView: View {
|
||||
|
||||
@Namespace private var namespace
|
||||
|
||||
// MARK: States
|
||||
|
||||
@State private var viewModel: BookmarksViewModel
|
||||
@ -95,7 +97,8 @@ struct BookmarksView: View {
|
||||
Task {
|
||||
await viewModel.toggleFavorite(bookmark: bookmark)
|
||||
}
|
||||
}
|
||||
},
|
||||
namespace: namespace
|
||||
)
|
||||
.onAppear {
|
||||
if bookmark.id == viewModel.bookmarks?.bookmarks.last?.id {
|
||||
@ -109,6 +112,7 @@ struct BookmarksView: View {
|
||||
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowBackground(Color(R.color.bookmark_list_bg))
|
||||
.matchedTransitionSource(id: bookmark.id, in: namespace)
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
@ -161,7 +165,8 @@ struct BookmarksView: View {
|
||||
set: { selectedBookmarkId = $0 }
|
||||
)
|
||||
) { bookmarkId in
|
||||
BookmarkDetailView(bookmarkId: bookmarkId)
|
||||
BookmarkDetailView(bookmarkId: bookmarkId, namespace: namespace)
|
||||
.navigationTransition(.zoom(sourceID: bookmarkId, in: namespace))
|
||||
}
|
||||
.sheet(isPresented: $showingAddBookmark) {
|
||||
AddBookmarkView(prefilledURL: shareURL, prefilledTitle: shareTitle)
|
||||
|
||||
@ -5,13 +5,15 @@ struct SearchBookmarksView: View {
|
||||
@FocusState private var searchFieldIsFocused: Bool
|
||||
@State private var selectedBookmarkId: String?
|
||||
@Binding var selectedBookmark: Bookmark?
|
||||
@Namespace private var namespace
|
||||
@State private var isFirstAppearance = true
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
HStack {
|
||||
Image(systemName: "magnifyingglass")
|
||||
.foregroundColor(.gray)
|
||||
TextField("Suchbegriff eingeben...", text: $viewModel.searchQuery)
|
||||
TextField("Search...", text: $viewModel.searchQuery)
|
||||
.focused($searchFieldIsFocused)
|
||||
.textFieldStyle(PlainTextFieldStyle())
|
||||
.autocapitalization(.none)
|
||||
@ -33,7 +35,7 @@ struct SearchBookmarksView: View {
|
||||
.padding([.horizontal, .top])
|
||||
|
||||
if viewModel.isLoading {
|
||||
ProgressView("Suche...")
|
||||
ProgressView("Searching...")
|
||||
.padding()
|
||||
}
|
||||
|
||||
@ -45,16 +47,7 @@ struct SearchBookmarksView: View {
|
||||
|
||||
if let bookmarks = viewModel.bookmarks?.bookmarks, !bookmarks.isEmpty {
|
||||
List(bookmarks) { bookmark in
|
||||
NavigationLink {
|
||||
BookmarkDetailView(bookmarkId: bookmark.id)
|
||||
} label: {
|
||||
BookmarkCardView(bookmark: bookmark, currentState: .all, onArchive: {_ in }, onDelete: {_ in }, onToggleFavorite: {_ in })
|
||||
.listRowBackground(Color(.systemBackground))
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
|
||||
|
||||
/*Button(action: {
|
||||
Button(action: {
|
||||
if UIDevice.isPhone {
|
||||
selectedBookmarkId = bookmark.id
|
||||
} else {
|
||||
@ -68,21 +61,44 @@ struct SearchBookmarksView: View {
|
||||
}
|
||||
}
|
||||
}) {
|
||||
BookmarkCardView(bookmark: bookmark, currentState: .all, onArchive: {_ in }, onDelete: {_ in }, onToggleFavorite: {_ in })
|
||||
.listRowBackground(Color(.systemBackground))
|
||||
.padding(.vertical, 4)
|
||||
BookmarkCardView(bookmark: bookmark, currentState: .all, onArchive: {_ in }, onDelete: {_ in }, onToggleFavorite: {_ in }, namespace: namespace)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
|
||||
.listRowSeparator(.hidden)
|
||||
*/
|
||||
.listRowBackground(Color(R.color.bookmark_list_bg))
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.background(Color(R.color.bookmark_list_bg))
|
||||
.scrollContentBackground(.hidden)
|
||||
.simultaneousGesture(
|
||||
DragGesture()
|
||||
.onChanged { _ in
|
||||
searchFieldIsFocused = false
|
||||
}
|
||||
)
|
||||
} else if !viewModel.isLoading && viewModel.bookmarks != nil {
|
||||
ContentUnavailableView("Keine Ergebnisse", systemImage: "magnifyingglass", description: Text("Keine Bookmarks gefunden."))
|
||||
ContentUnavailableView("No results", systemImage: "magnifyingglass", description: Text("No bookmarks found."))
|
||||
.padding()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.navigationTitle("Suche")
|
||||
.background(Color(R.color.bookmark_list_bg))
|
||||
.navigationTitle("Search")
|
||||
.navigationDestination(
|
||||
item: Binding<String?>(
|
||||
get: { selectedBookmarkId },
|
||||
set: { selectedBookmarkId = $0 }
|
||||
)
|
||||
) { bookmarkId in
|
||||
BookmarkDetailView(bookmarkId: bookmarkId, namespace: namespace)
|
||||
.navigationTransition(.zoom(sourceID: bookmarkId, in: namespace))
|
||||
}
|
||||
.onAppear {
|
||||
if isFirstAppearance {
|
||||
searchFieldIsFocused = true
|
||||
isFirstAppearance = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user