aboutsummaryrefslogtreecommitdiffstats
path: root/apps/mobile/lib
diff options
context:
space:
mode:
Diffstat (limited to 'apps/mobile/lib')
-rw-r--r--apps/mobile/lib/hooks.ts31
-rw-r--r--apps/mobile/lib/providers.tsx14
-rw-r--r--apps/mobile/lib/readerSettings.tsx93
-rw-r--r--apps/mobile/lib/session.ts9
-rw-r--r--apps/mobile/lib/settings.ts15
-rw-r--r--apps/mobile/lib/trpc.ts5
-rw-r--r--apps/mobile/lib/upload.ts17
-rw-r--r--apps/mobile/lib/useColorScheme.tsx12
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",
+ );
}