diff options
Diffstat (limited to 'apps/mobile/lib')
| -rw-r--r-- | apps/mobile/lib/hooks.ts | 31 | ||||
| -rw-r--r-- | apps/mobile/lib/providers.tsx | 14 | ||||
| -rw-r--r-- | apps/mobile/lib/readerSettings.tsx | 93 | ||||
| -rw-r--r-- | apps/mobile/lib/session.ts | 9 | ||||
| -rw-r--r-- | apps/mobile/lib/settings.ts | 15 | ||||
| -rw-r--r-- | apps/mobile/lib/trpc.ts | 5 | ||||
| -rw-r--r-- | apps/mobile/lib/upload.ts | 17 | ||||
| -rw-r--r-- | apps/mobile/lib/useColorScheme.tsx | 12 |
8 files changed, 165 insertions, 31 deletions
diff --git a/apps/mobile/lib/hooks.ts b/apps/mobile/lib/hooks.ts index 38ecebea..c3cb9d22 100644 --- a/apps/mobile/lib/hooks.ts +++ b/apps/mobile/lib/hooks.ts @@ -1,12 +1,39 @@ -import { ImageURISource } from "react-native"; +import { useQuery } from "@tanstack/react-query"; import useAppSettings from "./settings"; import { buildApiHeaders } from "./utils"; -export function useAssetUrl(assetId: string): ImageURISource { +interface AssetSource { + uri: string; + headers: Record<string, string>; +} + +export function useAssetUrl(assetId: string): AssetSource { const { settings } = useAppSettings(); return { uri: `${settings.address}/api/assets/${assetId}`, headers: buildApiHeaders(settings.apiKey, settings.customHeaders), }; } + +export function useServerVersion() { + const { settings } = useAppSettings(); + + return useQuery({ + queryKey: ["serverVersion", settings.address], + queryFn: async () => { + const response = await fetch(`${settings.address}/api/version`, { + headers: buildApiHeaders(settings.apiKey, settings.customHeaders), + }); + + if (!response.ok) { + throw new Error(`Failed to fetch server version: ${response.status}`); + } + + const data = await response.json(); + return data.version as string; + }, + enabled: !!settings.address, + staleTime: 1000 * 60 * 5, // Cache for 5 minutes + }); +} diff --git a/apps/mobile/lib/providers.tsx b/apps/mobile/lib/providers.tsx index 938b8aeb..4a7def1d 100644 --- a/apps/mobile/lib/providers.tsx +++ b/apps/mobile/lib/providers.tsx @@ -1,9 +1,10 @@ import { useEffect } from "react"; import FullPageSpinner from "@/components/ui/FullPageSpinner"; -import { ToastProvider } from "@/components/ui/Toast"; +import { Toaster } from "sonner-native"; -import { TRPCProvider } from "@karakeep/shared-react/providers/trpc-provider"; +import { TRPCSettingsProvider } from "@karakeep/shared-react/providers/trpc-provider"; +import { ReaderSettingsProvider } from "./readerSettings"; import useAppSettings from "./settings"; export function Providers({ children }: { children: React.ReactNode }) { @@ -19,8 +20,11 @@ export function Providers({ children }: { children: React.ReactNode }) { } return ( - <TRPCProvider settings={settings}> - <ToastProvider>{children}</ToastProvider> - </TRPCProvider> + <TRPCSettingsProvider settings={settings}> + <ReaderSettingsProvider> + {children} + <Toaster /> + </ReaderSettingsProvider> + </TRPCSettingsProvider> ); } diff --git a/apps/mobile/lib/readerSettings.tsx b/apps/mobile/lib/readerSettings.tsx new file mode 100644 index 00000000..9a3fc835 --- /dev/null +++ b/apps/mobile/lib/readerSettings.tsx @@ -0,0 +1,93 @@ +import { ReactNode, useCallback } from "react"; +import { Platform } from "react-native"; + +import { + ReaderSettingsProvider as BaseReaderSettingsProvider, + useReaderSettingsContext, +} from "@karakeep/shared-react/hooks/reader-settings"; +import { ReaderSettingsPartial } from "@karakeep/shared/types/readers"; +import { ZReaderFontFamily } from "@karakeep/shared/types/users"; + +import { useSettings } from "./settings"; + +// Mobile-specific font families for native Text components +// On Android, use generic font family names: "serif", "sans-serif", "monospace" +// On iOS, use specific font names like "Georgia" and "Courier" +// Note: undefined means use the system default font +export const MOBILE_FONT_FAMILIES: Record< + ZReaderFontFamily, + string | undefined +> = Platform.select({ + android: { + serif: "serif", + sans: undefined, + mono: "monospace", + }, + default: { + serif: "Georgia", + sans: undefined, + mono: "Courier", + }, +})!; + +// Font families for WebView HTML content (CSS font stacks) +export const WEBVIEW_FONT_FAMILIES: Record<ZReaderFontFamily, string> = { + serif: "Georgia, 'Times New Roman', serif", + sans: "-apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif", + mono: "ui-monospace, Menlo, Monaco, 'Courier New', monospace", +} as const; + +/** + * Mobile-specific provider for reader settings. + * Wraps the shared provider with mobile storage callbacks. + */ +export function ReaderSettingsProvider({ children }: { children: ReactNode }) { + // Read from zustand store directly to keep callback stable (empty deps). + const getLocalOverrides = useCallback((): ReaderSettingsPartial => { + const currentSettings = useSettings.getState().settings.settings; + return { + fontSize: currentSettings.readerFontSize, + lineHeight: currentSettings.readerLineHeight, + fontFamily: currentSettings.readerFontFamily, + }; + }, []); + + const saveLocalOverrides = useCallback((overrides: ReaderSettingsPartial) => { + const currentSettings = useSettings.getState().settings.settings; + // Remove reader settings keys first, then add back only defined ones + const { + readerFontSize: _fs, + readerLineHeight: _lh, + readerFontFamily: _ff, + ...rest + } = currentSettings; + + const newSettings = { ...rest }; + if (overrides.fontSize !== undefined) { + (newSettings as typeof currentSettings).readerFontSize = + overrides.fontSize; + } + if (overrides.lineHeight !== undefined) { + (newSettings as typeof currentSettings).readerLineHeight = + overrides.lineHeight; + } + if (overrides.fontFamily !== undefined) { + (newSettings as typeof currentSettings).readerFontFamily = + overrides.fontFamily; + } + + useSettings.getState().setSettings(newSettings); + }, []); + + return ( + <BaseReaderSettingsProvider + getLocalOverrides={getLocalOverrides} + saveLocalOverrides={saveLocalOverrides} + > + {children} + </BaseReaderSettingsProvider> + ); +} + +// Re-export the context hook as useReaderSettings for mobile consumers +export { useReaderSettingsContext as useReaderSettings }; diff --git a/apps/mobile/lib/session.ts b/apps/mobile/lib/session.ts index 8eb646cb..d6470145 100644 --- a/apps/mobile/lib/session.ts +++ b/apps/mobile/lib/session.ts @@ -1,12 +1,17 @@ import { useCallback } from "react"; +import { useMutation } from "@tanstack/react-query"; + +import { useTRPC } from "@karakeep/shared-react/trpc"; import useAppSettings from "./settings"; -import { api } from "./trpc"; export function useSession() { const { settings, setSettings } = useAppSettings(); + const api = useTRPC(); - const { mutate: deleteKey } = api.apiKeys.revoke.useMutation(); + const { mutate: deleteKey } = useMutation( + api.apiKeys.revoke.mutationOptions(), + ); const logout = useCallback(() => { if (settings.apiKeyId) { diff --git a/apps/mobile/lib/settings.ts b/apps/mobile/lib/settings.ts index 40a33976..8da1d33d 100644 --- a/apps/mobile/lib/settings.ts +++ b/apps/mobile/lib/settings.ts @@ -1,7 +1,10 @@ +import { useEffect } from "react"; import * as SecureStore from "expo-secure-store"; import { z } from "zod"; import { create } from "zustand"; +import { zReaderFontFamilySchema } from "@karakeep/shared/types/users"; + const SETTING_NAME = "settings"; const zSettingsSchema = z.object({ @@ -16,6 +19,10 @@ const zSettingsSchema = z.object({ .default("reader"), showNotes: z.boolean().optional().default(false), customHeaders: z.record(z.string(), z.string()).optional().default({}), + // Reader settings (local device overrides) + readerFontSize: z.number().int().min(12).max(24).optional(), + readerLineHeight: z.number().min(1.2).max(2.5).optional(), + readerFontFamily: zReaderFontFamilySchema.optional(), }); export type Settings = z.infer<typeof zSettingsSchema>; @@ -71,5 +78,13 @@ const useSettings = create<AppSettingsState>((set, get) => ({ export default function useAppSettings() { const { settings, setSettings, load } = useSettings(); + useEffect(() => { + if (settings.isLoading) { + load(); + } + }, [load, settings.isLoading]); + return { ...settings, setSettings, load }; } + +export { useSettings }; diff --git a/apps/mobile/lib/trpc.ts b/apps/mobile/lib/trpc.ts deleted file mode 100644 index e56968b8..00000000 --- a/apps/mobile/lib/trpc.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createTRPCReact } from "@trpc/react-query"; - -import type { AppRouter } from "@karakeep/trpc/routers/_app"; - -export const api = createTRPCReact<AppRouter>(); diff --git a/apps/mobile/lib/upload.ts b/apps/mobile/lib/upload.ts index 06f007f7..2f323ddb 100644 --- a/apps/mobile/lib/upload.ts +++ b/apps/mobile/lib/upload.ts @@ -1,6 +1,7 @@ import ReactNativeBlobUtil from "react-native-blob-util"; -import { useMutation } from "@tanstack/react-query"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useTRPC } from "@karakeep/shared-react/trpc"; import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks"; import { zUploadErrorSchema, @@ -8,7 +9,6 @@ import { } from "@karakeep/shared/types/uploads"; import type { Settings } from "./settings"; -import { api } from "./trpc"; import { buildApiHeaders } from "./utils"; export function useUploadAsset( @@ -18,13 +18,13 @@ export function useUploadAsset( onError?: (e: string) => void; }, ) { - const invalidateAllBookmarks = - api.useUtils().bookmarks.getBookmarks.invalidate; + const api = useTRPC(); + const queryClient = useQueryClient(); - const { mutate: createBookmark, isPending: isCreatingBookmark } = - api.bookmarks.createBookmark.useMutation({ + const { mutate: createBookmark, isPending: isCreatingBookmark } = useMutation( + api.bookmarks.createBookmark.mutationOptions({ onSuccess: (d) => { - invalidateAllBookmarks(); + queryClient.invalidateQueries(api.bookmarks.getBookmarks.pathFilter()); if (options.onSuccess) { options.onSuccess(d); } @@ -34,7 +34,8 @@ export function useUploadAsset( options.onError(e.message); } }, - }); + }), + ); const { mutate: uploadAsset, isPending: isUploading } = useMutation({ mutationFn: async (file: { type: string; name: string; uri: string }) => { diff --git a/apps/mobile/lib/useColorScheme.tsx b/apps/mobile/lib/useColorScheme.tsx index a00a445d..40e7ad53 100644 --- a/apps/mobile/lib/useColorScheme.tsx +++ b/apps/mobile/lib/useColorScheme.tsx @@ -46,13 +46,7 @@ function useInitialAndroidBarSync() { export { useColorScheme, useInitialAndroidBarSync }; function setNavigationBar(colorScheme: "light" | "dark") { - return Promise.all([ - NavigationBar.setButtonStyleAsync( - colorScheme === "dark" ? "light" : "dark", - ), - NavigationBar.setPositionAsync("absolute"), - NavigationBar.setBackgroundColorAsync( - colorScheme === "dark" ? "#00000030" : "#ffffff80", - ), - ]); + return NavigationBar.setButtonStyleAsync( + colorScheme === "dark" ? "light" : "dark", + ); } |
