| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- import AppKit
- import SwiftUI
- /// Google Books widget: search and shortcuts only. Results and library open in the in-app browser (no embedded web view).
- struct BooksDesktopWidgetView: View {
- let app: LauncherApp
- let mode: BooksWidgetMode
- var isPreview: Bool = false
- @State private var searchText = ""
- private var baseURL: URL { app.webURL ?? URL(string: "https://books.google.com")! }
- var body: some View {
- Group {
- switch mode {
- case .interactive:
- interactiveBody
- }
- }
- .clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous))
- }
- private var interactiveBody: some View {
- Group {
- if isPreview {
- interactivePreviewChrome
- } else {
- interactiveLiveChrome
- }
- }
- }
- private var interactivePreviewChrome: some View {
- ZStack {
- RoundedRectangle(cornerRadius: 22, style: .continuous)
- .fill(
- LinearGradient(
- colors: [Color(red: 0.2, green: 0.28, blue: 0.42), Color(red: 0.1, green: 0.12, blue: 0.22)],
- startPoint: .topLeading,
- endPoint: .bottomTrailing
- )
- )
- VStack(spacing: 12) {
- AppIconView(app: app, size: 48, showAppBackground: false, iconPaddingFactor: 0.08)
- Text("Google Books")
- .font(.system(size: 15, weight: .bold))
- .foregroundStyle(.white.opacity(0.92))
- Text("Search and shortcuts open Books in the app browser — no embedded page here.")
- .font(.system(size: 11, weight: .medium))
- .foregroundStyle(.white.opacity(0.65))
- .multilineTextAlignment(.center)
- .padding(.horizontal, 8)
- }
- .padding(16)
- }
- }
- private var interactiveLiveChrome: some View {
- VStack(spacing: 8) {
- interactiveHeader
- interactiveSearchBar
- primaryShortcutsRow
- secondaryShortcutsRow
- }
- .padding(.horizontal, 14)
- .padding(.vertical, 14)
- .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
- .background(
- RoundedRectangle(cornerRadius: 22, style: .continuous)
- .fill(Color(red: 0.08, green: 0.1, blue: 0.16))
- )
- }
- private var interactiveHeader: some View {
- HStack(spacing: 8) {
- AppIconView(app: app, size: 28, showAppBackground: false, iconPaddingFactor: 0.08)
- Text("Books")
- .font(.system(size: 14, weight: .bold))
- .foregroundStyle(.white.opacity(0.95))
- Spacer(minLength: 0)
- Button(action: {
- NSApp.activate(ignoringOtherApps: true)
- openBooksInAppBrowser(path: "/", queryItems: nil)
- }) {
- Image(systemName: "arrow.up.right.square")
- .font(.system(size: 13, weight: .semibold))
- .foregroundStyle(.white.opacity(0.9))
- .padding(8)
- .background(Circle().fill(Color.white.opacity(0.12)))
- }
- .buttonStyle(.plain)
- .help("Open Google Books in app browser")
- }
- }
- private var interactiveSearchBar: some View {
- HStack(spacing: 8) {
- Image(systemName: "magnifyingglass")
- .font(.system(size: 12, weight: .semibold))
- .foregroundStyle(.white.opacity(0.55))
- TextField("Search books & authors", text: $searchText)
- .textFieldStyle(.plain)
- .font(.system(size: 12, weight: .medium))
- .foregroundStyle(.white.opacity(0.95))
- .onSubmit { submitSearch() }
- Button("Search") { submitSearch() }
- .font(.system(size: 11, weight: .bold))
- .foregroundStyle(Color(red: 0.65, green: 0.82, blue: 1))
- .buttonStyle(.plain)
- }
- .padding(.horizontal, 10)
- .padding(.vertical, 8)
- .background(
- RoundedRectangle(cornerRadius: 10, style: .continuous)
- .fill(Color.white.opacity(0.08))
- )
- }
- private var primaryShortcutsRow: some View {
- VStack(alignment: .leading, spacing: 6) {
- Text("Library & reading")
- .font(.system(size: 10, weight: .bold))
- .foregroundStyle(.white.opacity(0.55))
- ScrollView(.horizontal, showsIndicators: false) {
- HStack(spacing: 8) {
- booksChip("Reading", systemImage: "book.fill", path: "/")
- booksChip("Library", systemImage: "books.vertical", path: "/ebooks/library")
- booksChip("Bookmarks", systemImage: "bookmark", path: "/ebooks/library")
- }
- }
- }
- }
- private var secondaryShortcutsRow: some View {
- VStack(alignment: .leading, spacing: 6) {
- Text("Discover")
- .font(.system(size: 10, weight: .bold))
- .foregroundStyle(.white.opacity(0.55))
- ScrollView(.horizontal, showsIndicators: false) {
- HStack(spacing: 8) {
- booksChip("Recommendations", systemImage: "sparkles", path: "/ebooks")
- booksChip("Audiobooks", systemImage: "headphones", path: "/audiobooks")
- }
- }
- }
- }
- private func booksChip(_ title: String, systemImage: String, path: String) -> some View {
- Button(action: {
- NSApp.activate(ignoringOtherApps: true)
- openBooksInAppBrowser(path: path, queryItems: nil)
- }) {
- HStack(spacing: 5) {
- Image(systemName: systemImage)
- .font(.system(size: 10, weight: .semibold))
- Text(title)
- .font(.system(size: 10, weight: .bold))
- }
- .foregroundStyle(.white.opacity(0.92))
- .padding(.horizontal, 10)
- .padding(.vertical, 6)
- .background(Capsule().fill(Color.white.opacity(0.12)))
- }
- .buttonStyle(.plain)
- .disabled(isPreview)
- .allowsHitTesting(!isPreview)
- }
- /// Builds `https://books.google.com` + path + optional query items. Query values are encoded once by `URLComponents`.
- private func booksURL(path: String, queryItems: [URLQueryItem]?) -> URL {
- var c = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)
- let p = path.hasPrefix("/") ? path : "/\(path)"
- c?.path = p
- c?.queryItems = queryItems
- return c?.url ?? baseURL
- }
- private func openBooksInAppBrowser(path: String, queryItems: [URLQueryItem]?) {
- guard !isPreview else { return }
- let url = booksURL(path: path, queryItems: queryItems)
- InAppBrowserWindowManager.shared.open(url: url, title: app.name)
- }
- /// Google Book Search uses `tbm=bks` on google.com (avoids broken `books.google.com/ebooks/search` and double-encoding bugs).
- private func submitSearch() {
- let q = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
- guard !q.isEmpty else { return }
- guard !isPreview else { return }
- NSApp.activate(ignoringOtherApps: true)
- var c = URLComponents(string: "https://www.google.com/search")
- c?.queryItems = [
- URLQueryItem(name: "tbm", value: "bks"),
- URLQueryItem(name: "q", value: q),
- ]
- guard let url = c?.url else { return }
- InAppBrowserWindowManager.shared.open(url: url, title: app.name)
- }
- }
|