diff --git a/app/(auth)/(tabs)/(home)/settings/logs/page.tsx b/app/(auth)/(tabs)/(home)/settings/logs/page.tsx index f94c4279..ce74d750 100644 --- a/app/(auth)/(tabs)/(home)/settings/logs/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/logs/page.tsx @@ -1,4 +1,6 @@ +import { Ionicons } from "@expo/vector-icons"; import { File, Paths } from "expo-file-system"; +import { requireOptionalNativeModule } from "expo-modules-core"; import { useNavigation } from "expo-router"; import type * as SharingType from "expo-sharing"; import { useCallback, useEffect, useId, useMemo, useState } from "react"; @@ -6,6 +8,7 @@ import { useTranslation } from "react-i18next"; import { Platform, ScrollView, TouchableOpacity, View } from "react-native"; import Collapsible from "react-native-collapsible"; import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { toast } from "sonner-native"; import { Text } from "@/components/common/Text"; import { FilterButton } from "@/components/filters/FilterButton"; import { Loader } from "@/components/Loader"; @@ -72,6 +75,25 @@ export default function Page() { } }, [filteredLogs, Sharing]); + const copyLog = useCallback( + async (log: NonNullable[number]) => { + // Skip on builds that don't ship the expo-clipboard native module + // (probe returns null instead of throwing); same guard as Quick Connect. + if (!requireOptionalNativeModule("ExpoClipboard")) return; + const Clipboard = await import("expo-clipboard"); + const text = [ + `[${log.level}] ${new Date(log.timestamp).toLocaleString()}`, + log.message, + log.data ? JSON.stringify(log.data, null, 2) : null, + ] + .filter(Boolean) + .join("\n"); + await Clipboard.setStringAsync(text); + toast.success(t("home.settings.logs.copied")); + }, + [logs, t], + ); + useEffect(() => { if (Platform.isTV) return; @@ -146,26 +168,41 @@ export default function Page() { {new Date(log.timestamp).toLocaleString()} - - {log.message} - + {log.message} + {/* Keep the whole collapsed row tappable: the hint lives inside + the toggle so tapping it expands too. */} + {log.data && !state[log.timestamp] && ( + + {t("home.settings.logs.click_for_more_info")} + + )} {log.data && ( - <> - {!state[log.timestamp] && ( - - {t("home.settings.logs.click_for_more_info")} - - )} - - - - {JSON.stringify(log.data, null, 2)} - - - - + + + + {/* Only the raw payload is selectable (per request); the + header/message stay tap-to-toggle. */} + {JSON.stringify(log.data, null, 2)} + + {!Platform.isTV && ( + copyLog(log)} + className='flex flex-row items-center self-end px-2 py-1' + > + + + {t("home.settings.logs.copy")} + + + )} + + )} ))} diff --git a/translations/en.json b/translations/en.json index 0a3e9683..49ad22a7 100644 --- a/translations/en.json +++ b/translations/en.json @@ -419,7 +419,9 @@ "click_for_more_info": "Click for more info", "level": "Level", "no_logs_available": "No logs available", - "delete_all_logs": "Delete all logs" + "delete_all_logs": "Delete all logs", + "copy": "Copy", + "copied": "Copied to clipboard" }, "languages": { "title": "Languages",