mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-07-16 01:13:27 +01:00
Compare commits
3 Commits
fix/auth-s
...
fix/ui-hea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b02cfcb65 | ||
|
|
3020bf5903 | ||
|
|
1f76119082 |
@@ -73,7 +73,6 @@ export default function IndexLayout() {
|
|||||||
headerLeft: () => (
|
headerLeft: () => (
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={() => _router.back()}
|
onPress={() => _router.back()}
|
||||||
className='pl-0.5'
|
|
||||||
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
|
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
|
||||||
>
|
>
|
||||||
<Feather name='chevron-left' size={28} color='white' />
|
<Feather name='chevron-left' size={28} color='white' />
|
||||||
@@ -158,7 +157,6 @@ export default function IndexLayout() {
|
|||||||
headerLeft: () => (
|
headerLeft: () => (
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={() => _router.back()}
|
onPress={() => _router.back()}
|
||||||
className='pl-0.5'
|
|
||||||
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
|
style={{ marginRight: Platform.OS === "android" ? 16 : 0 }}
|
||||||
>
|
>
|
||||||
<Feather name='chevron-left' size={28} color='white' />
|
<Feather name='chevron-left' size={28} color='white' />
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { File, Paths } from "expo-file-system";
|
import { File, Paths } from "expo-file-system";
|
||||||
|
import { requireOptionalNativeModule } from "expo-modules-core";
|
||||||
import { useNavigation } from "expo-router";
|
import { useNavigation } from "expo-router";
|
||||||
import type * as SharingType from "expo-sharing";
|
import type * as SharingType from "expo-sharing";
|
||||||
import { useCallback, useEffect, useId, useMemo, useState } from "react";
|
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 { Platform, ScrollView, TouchableOpacity, View } from "react-native";
|
||||||
import Collapsible from "react-native-collapsible";
|
import Collapsible from "react-native-collapsible";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
import { toast } from "sonner-native";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { FilterButton } from "@/components/filters/FilterButton";
|
import { FilterButton } from "@/components/filters/FilterButton";
|
||||||
import { Loader } from "@/components/Loader";
|
import { Loader } from "@/components/Loader";
|
||||||
@@ -72,6 +75,25 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
}, [filteredLogs, Sharing]);
|
}, [filteredLogs, Sharing]);
|
||||||
|
|
||||||
|
const copyLog = useCallback(
|
||||||
|
async (log: NonNullable<typeof logs>[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(() => {
|
useEffect(() => {
|
||||||
if (Platform.isTV) return;
|
if (Platform.isTV) return;
|
||||||
|
|
||||||
@@ -88,8 +110,15 @@ export default function Page() {
|
|||||||
}, [share, loading]);
|
}, [share, loading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='flex-1'>
|
<ScrollView
|
||||||
<View className='flex flex-row justify-end py-2 px-4 space-x-2'>
|
// Like the sibling settings pages, let iOS auto-inset the content below the
|
||||||
|
// transparent header (no manual header-height math). The filter bar is a
|
||||||
|
// sticky header so it stays pinned just under the header while logs scroll.
|
||||||
|
contentInsetAdjustmentBehavior='automatic'
|
||||||
|
stickyHeaderIndices={[0]}
|
||||||
|
contentContainerStyle={{ paddingBottom: insets.bottom }}
|
||||||
|
>
|
||||||
|
<View className='flex flex-row justify-end py-2 px-4 space-x-2 bg-black'>
|
||||||
<FilterButton
|
<FilterButton
|
||||||
id={orderFilterId}
|
id={orderFilterId}
|
||||||
queryKey='log'
|
queryKey='log'
|
||||||
@@ -112,67 +141,77 @@ export default function Page() {
|
|||||||
multiple={true}
|
multiple={true}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<ScrollView
|
<View className='flex flex-col space-y-2 px-4'>
|
||||||
className='pb-4 px-4'
|
{filteredLogs?.map((log, index) => (
|
||||||
contentContainerStyle={{ paddingBottom: insets.bottom }}
|
<View className='bg-neutral-900 rounded-xl p-3' key={index}>
|
||||||
>
|
<TouchableOpacity
|
||||||
<View className='flex flex-col space-y-2'>
|
disabled={!log.data}
|
||||||
{filteredLogs?.map((log, index) => (
|
onPress={() =>
|
||||||
<View className='bg-neutral-900 rounded-xl p-3' key={index}>
|
setState((v) => ({
|
||||||
<TouchableOpacity
|
...v,
|
||||||
disabled={!log.data}
|
[log.timestamp]: !v[log.timestamp],
|
||||||
onPress={() =>
|
}))
|
||||||
setState((v) => ({
|
}
|
||||||
...v,
|
>
|
||||||
[log.timestamp]: !v[log.timestamp],
|
<View className='flex flex-row justify-between'>
|
||||||
}))
|
<Text
|
||||||
}
|
className={`mb-1
|
||||||
>
|
|
||||||
<View className='flex flex-row justify-between'>
|
|
||||||
<Text
|
|
||||||
className={`mb-1
|
|
||||||
${log.level === "INFO" && "text-blue-500"}
|
${log.level === "INFO" && "text-blue-500"}
|
||||||
${log.level === "ERROR" && "text-red-500"}
|
${log.level === "ERROR" && "text-red-500"}
|
||||||
${log.level === "DEBUG" && "text-purple-500"}
|
${log.level === "DEBUG" && "text-purple-500"}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{log.level}
|
{log.level}
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text className='text-xs'>
|
|
||||||
{new Date(log.timestamp).toLocaleString()}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<Text selectable className='text-xs'>
|
|
||||||
{log.message}
|
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
{log.data && (
|
<Text className='text-xs'>
|
||||||
<>
|
{new Date(log.timestamp).toLocaleString()}
|
||||||
{!state[log.timestamp] && (
|
</Text>
|
||||||
<Text className='text-xs mt-0.5'>
|
</View>
|
||||||
{t("home.settings.logs.click_for_more_info")}
|
<Text className='text-xs'>{log.message}</Text>
|
||||||
</Text>
|
{/* Keep the whole collapsed row tappable: the hint lives inside
|
||||||
)}
|
the toggle so tapping it expands too. */}
|
||||||
<Collapsible collapsed={!state[log.timestamp]}>
|
{log.data && !state[log.timestamp] && (
|
||||||
<View className='mt-2 flex flex-col space-y-2'>
|
<Text className='text-xs mt-0.5'>
|
||||||
<ScrollView className='rounded-xl' style={codeBlockStyle}>
|
{t("home.settings.logs.click_for_more_info")}
|
||||||
<Text>{JSON.stringify(log.data, null, 2)}</Text>
|
</Text>
|
||||||
</ScrollView>
|
|
||||||
</View>
|
|
||||||
</Collapsible>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</View>
|
</TouchableOpacity>
|
||||||
))}
|
|
||||||
{filteredLogs?.length === 0 && (
|
{log.data && (
|
||||||
<Text className='opacity-50'>
|
<Collapsible collapsed={!state[log.timestamp]}>
|
||||||
{t("home.settings.logs.no_logs_available")}
|
<View className='mt-2 flex flex-col space-y-2'>
|
||||||
</Text>
|
<ScrollView
|
||||||
)}
|
className='rounded-xl'
|
||||||
</View>
|
style={codeBlockStyle}
|
||||||
</ScrollView>
|
nestedScrollEnabled
|
||||||
</View>
|
>
|
||||||
|
{/* Only the raw payload is selectable (per request); the
|
||||||
|
header/message stay tap-to-toggle. */}
|
||||||
|
<Text selectable>{JSON.stringify(log.data, null, 2)}</Text>
|
||||||
|
</ScrollView>
|
||||||
|
{!Platform.isTV && (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => copyLog(log)}
|
||||||
|
className='flex flex-row items-center self-end px-2 py-1'
|
||||||
|
>
|
||||||
|
<Ionicons name='copy-outline' size={16} color='white' />
|
||||||
|
<Text className='text-xs ml-1'>
|
||||||
|
{t("home.settings.logs.copy")}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</Collapsible>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
{filteredLogs?.length === 0 && (
|
||||||
|
<Text className='opacity-50'>
|
||||||
|
{t("home.settings.logs.no_logs_available")}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
3
bun.lock
3
bun.lock
@@ -31,6 +31,7 @@
|
|||||||
"expo-brightness": "~56.0.5",
|
"expo-brightness": "~56.0.5",
|
||||||
"expo-build-properties": "~56.0.18",
|
"expo-build-properties": "~56.0.18",
|
||||||
"expo-camera": "~56.0.8",
|
"expo-camera": "~56.0.8",
|
||||||
|
"expo-clipboard": "~56.0.4",
|
||||||
"expo-constants": "~56.0.18",
|
"expo-constants": "~56.0.18",
|
||||||
"expo-crypto": "~56.0.4",
|
"expo-crypto": "~56.0.4",
|
||||||
"expo-dev-client": "~56.0.20",
|
"expo-dev-client": "~56.0.20",
|
||||||
@@ -1001,6 +1002,8 @@
|
|||||||
|
|
||||||
"expo-camera": ["expo-camera@56.0.8", "", { "dependencies": { "barcode-detector": "^3.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-UDOpUUMisFRmCv1XQV1MJCKGAH2CsIC1Rs6P9Bbc6JLVmbxEKAd5dK68y6cScOdWURxVfJ0PRcjYnSuc8ayyIQ=="],
|
"expo-camera": ["expo-camera@56.0.8", "", { "dependencies": { "barcode-detector": "^3.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-UDOpUUMisFRmCv1XQV1MJCKGAH2CsIC1Rs6P9Bbc6JLVmbxEKAd5dK68y6cScOdWURxVfJ0PRcjYnSuc8ayyIQ=="],
|
||||||
|
|
||||||
|
"expo-clipboard": ["expo-clipboard@56.0.4", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-qb4DYlkiowHYHaUYVT2FN9nk/nI1xShXOUYsI7J9dVpQCOHcGFjCBPX1VAvEW4Ye4/Aagd6IuhOVAq/+scBOiA=="],
|
||||||
|
|
||||||
"expo-constants": ["expo-constants@56.0.18", "", { "dependencies": { "@expo/env": "~2.3.0" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-8AMtbDGl/WVPnWlmbpGmvcdnNCy9E4PFnwdVwj600vljkMDPSxcAcjw8GVXEPk3PpZ+ngTqsrkltWyj0UKYAxw=="],
|
"expo-constants": ["expo-constants@56.0.18", "", { "dependencies": { "@expo/env": "~2.3.0" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-8AMtbDGl/WVPnWlmbpGmvcdnNCy9E4PFnwdVwj600vljkMDPSxcAcjw8GVXEPk3PpZ+ngTqsrkltWyj0UKYAxw=="],
|
||||||
|
|
||||||
"expo-crypto": ["expo-crypto@56.0.4", "", { "peerDependencies": { "expo": "*" } }, "sha512-fRNEhoXRXgAWBpe3/hq5X+KXTit3OZqdiAGts1YvNEUHQb+H5591mpPac0Yw+sZg9pXcrjRnzo5AxvZaENpc7g=="],
|
"expo-crypto": ["expo-crypto@56.0.4", "", { "peerDependencies": { "expo": "*" } }, "sha512-fRNEhoXRXgAWBpe3/hq5X+KXTit3OZqdiAGts1YvNEUHQb+H5591mpPac0Yw+sZg9pXcrjRnzo5AxvZaENpc7g=="],
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Feather } from "@expo/vector-icons";
|
||||||
import { BlurView, type BlurViewProps } from "expo-blur";
|
import { BlurView, type BlurViewProps } from "expo-blur";
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { Pressable, type PressableProps } from "react-native-gesture-handler";
|
import { Pressable, type PressableProps } from "react-native-gesture-handler";
|
||||||
@@ -23,7 +23,7 @@ export const HeaderBackButton: React.FC<Props> = ({
|
|||||||
className='flex items-center justify-center w-9 h-9'
|
className='flex items-center justify-center w-9 h-9'
|
||||||
{...pressableProps}
|
{...pressableProps}
|
||||||
>
|
>
|
||||||
<Ionicons name='arrow-back' size={24} color='white' />
|
<Feather name='chevron-left' size={28} color='white' />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -36,10 +36,10 @@ export const HeaderBackButton: React.FC<Props> = ({
|
|||||||
intensity={100}
|
intensity={100}
|
||||||
className='overflow-hidden rounded-full p-2'
|
className='overflow-hidden rounded-full p-2'
|
||||||
>
|
>
|
||||||
<Ionicons
|
<Feather
|
||||||
className='drop-shadow-2xl'
|
className='drop-shadow-2xl'
|
||||||
name='arrow-back'
|
name='chevron-left'
|
||||||
size={24}
|
size={28}
|
||||||
color='white'
|
color='white'
|
||||||
/>
|
/>
|
||||||
</BlurView>
|
</BlurView>
|
||||||
@@ -49,13 +49,16 @@ export const HeaderBackButton: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={() => router.back()}
|
onPress={() => router.back()}
|
||||||
className=' rounded-full p-2'
|
// Match the Settings page back button: chevron flush to the edge with a
|
||||||
|
// 16px gap before the title (the old `p-2` pushed both arrow and title
|
||||||
|
// too far right). drop-shadow keeps it readable over images.
|
||||||
|
style={{ marginRight: 16 }}
|
||||||
{...pressableProps}
|
{...pressableProps}
|
||||||
>
|
>
|
||||||
<Ionicons
|
<Feather
|
||||||
className='drop-shadow-2xl'
|
className='drop-shadow-2xl'
|
||||||
name='arrow-back'
|
name='chevron-left'
|
||||||
size={24}
|
size={28}
|
||||||
color='white'
|
color='white'
|
||||||
/>
|
/>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
"expo-brightness": "~56.0.5",
|
"expo-brightness": "~56.0.5",
|
||||||
"expo-build-properties": "~56.0.18",
|
"expo-build-properties": "~56.0.18",
|
||||||
"expo-camera": "~56.0.8",
|
"expo-camera": "~56.0.8",
|
||||||
|
"expo-clipboard": "~56.0.4",
|
||||||
"expo-constants": "~56.0.18",
|
"expo-constants": "~56.0.18",
|
||||||
"expo-crypto": "~56.0.4",
|
"expo-crypto": "~56.0.4",
|
||||||
"expo-dev-client": "~56.0.20",
|
"expo-dev-client": "~56.0.20",
|
||||||
|
|||||||
@@ -410,7 +410,9 @@
|
|||||||
"click_for_more_info": "Click for more info",
|
"click_for_more_info": "Click for more info",
|
||||||
"level": "Level",
|
"level": "Level",
|
||||||
"no_logs_available": "No logs available",
|
"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": {
|
"languages": {
|
||||||
"title": "Languages",
|
"title": "Languages",
|
||||||
|
|||||||
Reference in New Issue
Block a user