mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-07-19 18:54:16 +01:00
Compare commits
60 Commits
renovate/r
...
renovate/l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d01019798 | ||
|
|
e481f22e10 | ||
|
|
4487682412 | ||
|
|
994056d5f0 | ||
|
|
31cf9e8dff | ||
|
|
3a77ffc63c | ||
|
|
e6200a09ad | ||
|
|
fe36e2e0fc | ||
|
|
a1ba5d8c1d | ||
|
|
299d80ce03 | ||
|
|
1eaca76e97 | ||
|
|
9d9da134c0 | ||
|
|
c3275cda34 | ||
|
|
cd60c0ca07 | ||
|
|
6f6e3ed49f | ||
|
|
fd4215407d | ||
|
|
71b82a75ee | ||
|
|
e0cf1559bc | ||
|
|
610158efbd | ||
|
|
a2802b4570 | ||
|
|
9f08b51e1e | ||
|
|
06076b1a85 | ||
|
|
7d8014f686 | ||
|
|
4813fdab81 | ||
|
|
d5d0976ff9 | ||
|
|
fd504cef14 | ||
|
|
dbdf4767a5 | ||
|
|
1860863c60 | ||
|
|
69651cdfa3 | ||
|
|
25ee7dad85 | ||
|
|
34d9b43dcb | ||
|
|
039d7cfaec | ||
|
|
9cdfebe908 | ||
|
|
bee8323110 | ||
|
|
8b02cfcb65 | ||
|
|
3020bf5903 | ||
|
|
1f76119082 | ||
|
|
e0017b8092 | ||
|
|
4c5a3dc658 | ||
|
|
b39eb35795 | ||
|
|
c4b6390a5e | ||
|
|
3ff3847d80 | ||
|
|
8cf141e03b | ||
|
|
34f01e2c13 | ||
|
|
b7bdd99cdd | ||
|
|
6ba1111794 | ||
|
|
9fea9015eb | ||
|
|
500da94583 | ||
|
|
98dfefbdd0 | ||
|
|
fb8f11460b | ||
|
|
112d9bef96 | ||
|
|
692e02e42f | ||
|
|
49a903f48a | ||
|
|
8618e2d6c3 | ||
|
|
fbeb025d73 | ||
|
|
64b9224b7a | ||
|
|
d9677cbece | ||
|
|
97ef9b5ee7 | ||
|
|
ff708ec832 | ||
|
|
d6980cfc8e |
@@ -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' />
|
||||||
|
|||||||
@@ -324,32 +324,44 @@ export default function SettingsTV() {
|
|||||||
// MPV alignment options
|
// MPV alignment options
|
||||||
const alignXOptions: TVOptionItem<string>[] = useMemo(
|
const alignXOptions: TVOptionItem<string>[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{ label: "Left", value: "left", selected: currentAlignX === "left" },
|
|
||||||
{
|
{
|
||||||
label: "Center",
|
label: t("home.settings.subtitles.align.left"),
|
||||||
|
value: "left",
|
||||||
|
selected: currentAlignX === "left",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("home.settings.subtitles.align.center"),
|
||||||
value: "center",
|
value: "center",
|
||||||
selected: currentAlignX === "center",
|
selected: currentAlignX === "center",
|
||||||
},
|
},
|
||||||
{ label: "Right", value: "right", selected: currentAlignX === "right" },
|
{
|
||||||
|
label: t("home.settings.subtitles.align.right"),
|
||||||
|
value: "right",
|
||||||
|
selected: currentAlignX === "right",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
[currentAlignX],
|
[currentAlignX, t],
|
||||||
);
|
);
|
||||||
|
|
||||||
const alignYOptions: TVOptionItem<string>[] = useMemo(
|
const alignYOptions: TVOptionItem<string>[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{ label: "Top", value: "top", selected: currentAlignY === "top" },
|
|
||||||
{
|
{
|
||||||
label: "Center",
|
label: t("home.settings.subtitles.align.top"),
|
||||||
|
value: "top",
|
||||||
|
selected: currentAlignY === "top",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("home.settings.subtitles.align.center"),
|
||||||
value: "center",
|
value: "center",
|
||||||
selected: currentAlignY === "center",
|
selected: currentAlignY === "center",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Bottom",
|
label: t("home.settings.subtitles.align.bottom"),
|
||||||
value: "bottom",
|
value: "bottom",
|
||||||
selected: currentAlignY === "bottom",
|
selected: currentAlignY === "bottom",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[currentAlignY],
|
[currentAlignY, t],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Cache mode options
|
// Cache mode options
|
||||||
@@ -499,13 +511,13 @@ export default function SettingsTV() {
|
|||||||
|
|
||||||
const alignXLabel = useMemo(() => {
|
const alignXLabel = useMemo(() => {
|
||||||
const option = alignXOptions.find((o) => o.selected);
|
const option = alignXOptions.find((o) => o.selected);
|
||||||
return option?.label || "Center";
|
return option?.label || t("home.settings.subtitles.align.center");
|
||||||
}, [alignXOptions]);
|
}, [alignXOptions, t]);
|
||||||
|
|
||||||
const alignYLabel = useMemo(() => {
|
const alignYLabel = useMemo(() => {
|
||||||
const option = alignYOptions.find((o) => o.selected);
|
const option = alignYOptions.find((o) => o.selected);
|
||||||
return option?.label || "Bottom";
|
return option?.label || t("home.settings.subtitles.align.bottom");
|
||||||
}, [alignYOptions]);
|
}, [alignYOptions, t]);
|
||||||
|
|
||||||
const typographyScaleLabel = useMemo(() => {
|
const typographyScaleLabel = useMemo(() => {
|
||||||
const option = typographyScaleOptions.find((o) => o.selected);
|
const option = typographyScaleOptions.find((o) => o.selected);
|
||||||
@@ -645,7 +657,7 @@ export default function SettingsTV() {
|
|||||||
formatValue={(v) => `${v.toFixed(1)}x`}
|
formatValue={(v) => `${v.toFixed(1)}x`}
|
||||||
/>
|
/>
|
||||||
<TVSettingsStepper
|
<TVSettingsStepper
|
||||||
label='Vertical Margin'
|
label={t("home.settings.subtitles.mpv_subtitle_margin_y")}
|
||||||
value={settings.mpvSubtitleMarginY ?? 0}
|
value={settings.mpvSubtitleMarginY ?? 0}
|
||||||
onDecrease={() => {
|
onDecrease={() => {
|
||||||
const newValue = Math.max(
|
const newValue = Math.max(
|
||||||
@@ -663,11 +675,11 @@ export default function SettingsTV() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TVSettingsOptionButton
|
<TVSettingsOptionButton
|
||||||
label='Horizontal Alignment'
|
label={t("home.settings.subtitles.mpv_subtitle_align_x")}
|
||||||
value={alignXLabel}
|
value={alignXLabel}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
showOptions({
|
showOptions({
|
||||||
title: "Horizontal Alignment",
|
title: t("home.settings.subtitles.mpv_subtitle_align_x"),
|
||||||
options: alignXOptions,
|
options: alignXOptions,
|
||||||
onSelect: (value) =>
|
onSelect: (value) =>
|
||||||
updateSettings({
|
updateSettings({
|
||||||
@@ -677,11 +689,11 @@ export default function SettingsTV() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<TVSettingsOptionButton
|
<TVSettingsOptionButton
|
||||||
label='Vertical Alignment'
|
label={t("home.settings.subtitles.mpv_subtitle_align_y")}
|
||||||
value={alignYLabel}
|
value={alignYLabel}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
showOptions({
|
showOptions({
|
||||||
title: "Vertical Alignment",
|
title: t("home.settings.subtitles.mpv_subtitle_align_y"),
|
||||||
options: alignYOptions,
|
options: alignYOptions,
|
||||||
onSelect: (value) =>
|
onSelect: (value) =>
|
||||||
updateSettings({
|
updateSettings({
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { getUserViewsApi } from "@jellyfin/sdk/lib/utils/api";
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ScrollView, Switch, View } from "react-native";
|
import { ScrollView, View } from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
import { SettingSwitch } from "@/components/common/SettingSwitch";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { Loader } from "@/components/Loader";
|
import { Loader } from "@/components/Loader";
|
||||||
import { ListGroup } from "@/components/list/ListGroup";
|
import { ListGroup } from "@/components/list/ListGroup";
|
||||||
@@ -50,12 +51,12 @@ export default function AppearanceHideLibrariesPage() {
|
|||||||
>
|
>
|
||||||
<DisabledSetting
|
<DisabledSetting
|
||||||
disabled={pluginSettings?.hiddenLibraries?.locked === true}
|
disabled={pluginSettings?.hiddenLibraries?.locked === true}
|
||||||
className='px-4'
|
className='px-4 pt-4'
|
||||||
>
|
>
|
||||||
<ListGroup title={t("home.settings.other.hide_libraries")}>
|
<ListGroup title={t("home.settings.other.hide_libraries")}>
|
||||||
{data?.map((view) => (
|
{data?.map((view) => (
|
||||||
<ListItem key={view.Id} title={view.Name} onPress={() => {}}>
|
<ListItem key={view.Id} title={view.Name} onPress={() => {}}>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.hiddenLibraries?.includes(view.Id!) || false}
|
value={settings.hiddenLibraries?.includes(view.Id!) || false}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
updateSettings({
|
updateSettings({
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { getUserViewsApi } from "@jellyfin/sdk/lib/utils/api";
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Switch, View } from "react-native";
|
import { View } from "react-native";
|
||||||
|
import { SettingSwitch } from "@/components/common/SettingSwitch";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { Loader } from "@/components/Loader";
|
import { Loader } from "@/components/Loader";
|
||||||
import { ListGroup } from "@/components/list/ListGroup";
|
import { ListGroup } from "@/components/list/ListGroup";
|
||||||
@@ -46,7 +47,7 @@ export default function HideLibrariesPage() {
|
|||||||
<ListGroup>
|
<ListGroup>
|
||||||
{data?.map((view) => (
|
{data?.map((view) => (
|
||||||
<ListItem key={view.Id} title={view.Name} onPress={() => {}}>
|
<ListItem key={view.Id} title={view.Name} onPress={() => {}}>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.hiddenLibraries?.includes(view.Id!) || false}
|
value={settings.hiddenLibraries?.includes(view.Id!) || false}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
updateSettings({
|
updateSettings({
|
||||||
|
|||||||
@@ -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,9 +3,9 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Platform, ScrollView, View } from "react-native";
|
import { Platform, ScrollView, View } from "react-native";
|
||||||
import { Switch } from "react-native-gesture-handler";
|
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { toast } from "sonner-native";
|
import { toast } from "sonner-native";
|
||||||
|
import { SettingSwitch } from "@/components/common/SettingSwitch";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { ListGroup } from "@/components/list/ListGroup";
|
import { ListGroup } from "@/components/list/ListGroup";
|
||||||
import { ListItem } from "@/components/list/ListItem";
|
import { ListItem } from "@/components/list/ListItem";
|
||||||
@@ -136,7 +136,7 @@ export default function MusicSettingsPage() {
|
|||||||
title={t("home.settings.music.prefer_downloaded")}
|
title={t("home.settings.music.prefer_downloaded")}
|
||||||
disabled={pluginSettings?.preferLocalAudio?.locked}
|
disabled={pluginSettings?.preferLocalAudio?.locked}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.preferLocalAudio}
|
value={settings.preferLocalAudio}
|
||||||
disabled={pluginSettings?.preferLocalAudio?.locked}
|
disabled={pluginSettings?.preferLocalAudio?.locked}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
@@ -159,7 +159,7 @@ export default function MusicSettingsPage() {
|
|||||||
title={t("home.settings.music.lookahead_enabled")}
|
title={t("home.settings.music.lookahead_enabled")}
|
||||||
disabled={pluginSettings?.audioLookaheadEnabled?.locked}
|
disabled={pluginSettings?.audioLookaheadEnabled?.locked}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.audioLookaheadEnabled}
|
value={settings.audioLookaheadEnabled}
|
||||||
disabled={pluginSettings?.audioLookaheadEnabled?.locked}
|
disabled={pluginSettings?.audioLookaheadEnabled?.locked}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
@@ -233,7 +233,7 @@ export default function MusicSettingsPage() {
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
<ListGroup>
|
<ListGroup className='mt-4'>
|
||||||
<ListItem
|
<ListItem
|
||||||
textColor='red'
|
textColor='red'
|
||||||
onPress={onDeleteDownloadedSongsClicked}
|
onPress={onDeleteDownloadedSongsClicked}
|
||||||
|
|||||||
@@ -17,13 +17,14 @@ export default function PlaybackControlsPage() {
|
|||||||
contentContainerStyle={{
|
contentContainerStyle={{
|
||||||
paddingLeft: insets.left,
|
paddingLeft: insets.left,
|
||||||
paddingRight: insets.right,
|
paddingRight: insets.right,
|
||||||
|
paddingBottom: insets.bottom,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
className='p-4 flex flex-col'
|
className='p-4 flex flex-col'
|
||||||
style={{ paddingTop: Platform.OS === "android" ? 10 : 0 }}
|
style={{ paddingTop: Platform.OS === "android" ? 10 : 0 }}
|
||||||
>
|
>
|
||||||
<View className='mb-4'>
|
<View>
|
||||||
<MediaProvider>
|
<MediaProvider>
|
||||||
<MediaToggles className='mb-4' />
|
<MediaToggles className='mb-4' />
|
||||||
<GestureControls className='mb-4' />
|
<GestureControls className='mb-4' />
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import { ScrollView } from "react-native";
|
import { ScrollView, View } from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import DisabledSetting from "@/components/settings/DisabledSetting";
|
|
||||||
import { JellyseerrSettings } from "@/components/settings/Jellyseerr";
|
import { JellyseerrSettings } from "@/components/settings/Jellyseerr";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
|
||||||
|
|
||||||
export default function JellyseerrPluginPage() {
|
export default function JellyseerrPluginPage() {
|
||||||
const { pluginSettings } = useSettings();
|
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -16,12 +13,9 @@ export default function JellyseerrPluginPage() {
|
|||||||
paddingRight: insets.right,
|
paddingRight: insets.right,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DisabledSetting
|
<View className='p-4'>
|
||||||
disabled={pluginSettings?.jellyseerrServerUrl?.locked === true}
|
|
||||||
className='p-4'
|
|
||||||
>
|
|
||||||
<JellyseerrSettings />
|
<JellyseerrSettings />
|
||||||
</DisabledSetting>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import { ScrollView } from "react-native";
|
import { ScrollView, View } from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import DisabledSetting from "@/components/settings/DisabledSetting";
|
|
||||||
import { KefinTweaksSettings } from "@/components/settings/KefinTweaks";
|
import { KefinTweaksSettings } from "@/components/settings/KefinTweaks";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
|
||||||
|
|
||||||
export default function KefinTweaksPage() {
|
export default function KefinTweaksPage() {
|
||||||
const { pluginSettings } = useSettings();
|
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -16,12 +13,9 @@ export default function KefinTweaksPage() {
|
|||||||
paddingRight: insets.right,
|
paddingRight: insets.right,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DisabledSetting
|
<View className='px-4'>
|
||||||
disabled={pluginSettings?.useKefinTweaks?.locked === true}
|
|
||||||
className='p-4'
|
|
||||||
>
|
|
||||||
<KefinTweaksSettings />
|
<KefinTweaksSettings />
|
||||||
</DisabledSetting>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,37 @@
|
|||||||
import { useNavigation } from "expo-router";
|
import { useNavigation } from "expo-router";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
Linking,
|
Linking,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
Switch,
|
|
||||||
TextInput,
|
TextInput,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { toast } from "sonner-native";
|
import { toast } from "sonner-native";
|
||||||
|
import { SettingSwitch } from "@/components/common/SettingSwitch";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { ListGroup } from "@/components/list/ListGroup";
|
import { ListGroup } from "@/components/list/ListGroup";
|
||||||
import { ListItem } from "@/components/list/ListItem";
|
import { ListItem } from "@/components/list/ListItem";
|
||||||
import DisabledSetting from "@/components/settings/DisabledSetting";
|
|
||||||
import { useNetworkAwareQueryClient } from "@/hooks/useNetworkAwareQueryClient";
|
import { useNetworkAwareQueryClient } from "@/hooks/useNetworkAwareQueryClient";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
|
|
||||||
export default function MarlinSearchPage() {
|
export default function MarlinSearchPage() {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
const { settings, updateSettings, pluginSettings } = useSettings();
|
const { settings, updateSettings, pluginSettings } = useSettings();
|
||||||
const queryClient = useNetworkAwareQueryClient();
|
const queryClient = useNetworkAwareQueryClient();
|
||||||
|
|
||||||
const [value, setValue] = useState<string>(settings?.marlinServerUrl || "");
|
const [value, setValue] = useState<string>(settings?.marlinServerUrl || "");
|
||||||
|
|
||||||
|
const searchEngineLocked = pluginSettings?.searchEngine?.locked === true;
|
||||||
|
const marlinUrlLocked = pluginSettings?.marlinServerUrl?.locked === true;
|
||||||
|
// Effective (user/admin merged) URL, same source the search screen uses —
|
||||||
|
// the raw plugin value misses a user-configured Streamystats.
|
||||||
|
const hasStreamystats = !!settings?.streamyStatsServerUrl;
|
||||||
|
|
||||||
const onSave = (val: string) => {
|
const onSave = (val: string) => {
|
||||||
updateSettings({
|
updateSettings({
|
||||||
marlinServerUrl: !val.endsWith("/") ? val : val.slice(0, -1),
|
marlinServerUrl: !val.endsWith("/") ? val : val.slice(0, -1),
|
||||||
@@ -41,15 +43,8 @@ export default function MarlinSearchPage() {
|
|||||||
Linking.openURL("https://github.com/fredrikburmester/marlin-search");
|
Linking.openURL("https://github.com/fredrikburmester/marlin-search");
|
||||||
};
|
};
|
||||||
|
|
||||||
const disabled = useMemo(() => {
|
|
||||||
return (
|
|
||||||
pluginSettings?.searchEngine?.locked === true &&
|
|
||||||
pluginSettings?.marlinServerUrl?.locked === true
|
|
||||||
);
|
|
||||||
}, [pluginSettings]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!pluginSettings?.marlinServerUrl?.locked) {
|
if (!marlinUrlLocked) {
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<TouchableOpacity onPress={() => onSave(value)} className='px-2'>
|
<TouchableOpacity onPress={() => onSave(value)} className='px-2'>
|
||||||
@@ -60,7 +55,7 @@ export default function MarlinSearchPage() {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [navigation, value, pluginSettings?.marlinServerUrl?.locked, t]);
|
}, [navigation, value, marlinUrlLocked, t]);
|
||||||
|
|
||||||
if (!settings) return null;
|
if (!settings) return null;
|
||||||
|
|
||||||
@@ -72,52 +67,42 @@ export default function MarlinSearchPage() {
|
|||||||
paddingRight: insets.right,
|
paddingRight: insets.right,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DisabledSetting disabled={disabled} className='px-4'>
|
<View className='px-4 pt-4'>
|
||||||
<ListGroup>
|
<ListGroup>
|
||||||
<DisabledSetting
|
{/* disabledByAdmin renders the "Disabled by admin" notice as the row's
|
||||||
disabled={
|
subtitle (same pattern as the Streamystats settings) — no clipping. */}
|
||||||
pluginSettings?.searchEngine?.locked === true ||
|
<ListItem
|
||||||
!!pluginSettings?.streamyStatsServerUrl?.value
|
title={t(
|
||||||
}
|
"home.settings.plugins.marlin_search.enable_marlin_search",
|
||||||
showText={!pluginSettings?.marlinServerUrl?.locked}
|
)}
|
||||||
|
disabledByAdmin={searchEngineLocked}
|
||||||
|
// Streamystats owns the search engine while configured — block the
|
||||||
|
// row tap too, not just the Switch, so it can't force "Jellyfin".
|
||||||
|
disabled={hasStreamystats}
|
||||||
|
onPress={() => {
|
||||||
|
updateSettings({ searchEngine: "Jellyfin" });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["search"] });
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ListItem
|
<SettingSwitch
|
||||||
title={t(
|
value={settings.searchEngine === "Marlin"}
|
||||||
"home.settings.plugins.marlin_search.enable_marlin_search",
|
disabled={searchEngineLocked || hasStreamystats}
|
||||||
)}
|
onValueChange={(val) => {
|
||||||
onPress={() => {
|
updateSettings({ searchEngine: val ? "Marlin" : "Jellyfin" });
|
||||||
updateSettings({ searchEngine: "Jellyfin" });
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["search"] });
|
queryClient.invalidateQueries({ queryKey: ["search"] });
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<Switch
|
</ListItem>
|
||||||
value={settings.searchEngine === "Marlin"}
|
|
||||||
disabled={!!pluginSettings?.streamyStatsServerUrl?.value}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
updateSettings({
|
|
||||||
searchEngine: value ? "Marlin" : "Jellyfin",
|
|
||||||
});
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["search"] });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
</DisabledSetting>
|
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
|
|
||||||
<DisabledSetting
|
<ListGroup className='mt-2'>
|
||||||
disabled={pluginSettings?.marlinServerUrl?.locked === true}
|
<ListItem
|
||||||
showText={!pluginSettings?.searchEngine?.locked}
|
title={t("home.settings.plugins.marlin_search.url")}
|
||||||
className='mt-2 flex flex-col rounded-xl overflow-hidden pl-4 bg-neutral-900 px-4'
|
disabledByAdmin={marlinUrlLocked}
|
||||||
>
|
|
||||||
<View
|
|
||||||
className={"flex flex-row items-center bg-neutral-900 h-11 pr-4"}
|
|
||||||
>
|
>
|
||||||
<Text className='mr-4'>
|
|
||||||
{t("home.settings.plugins.marlin_search.url")}
|
|
||||||
</Text>
|
|
||||||
<TextInput
|
<TextInput
|
||||||
editable={settings.searchEngine === "Marlin"}
|
editable={!marlinUrlLocked && settings.searchEngine === "Marlin"}
|
||||||
className='text-white'
|
className='text-white text-right flex-1'
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
"home.settings.plugins.marlin_search.server_url_placeholder",
|
"home.settings.plugins.marlin_search.server_url_placeholder",
|
||||||
)}
|
)}
|
||||||
@@ -128,15 +113,16 @@ export default function MarlinSearchPage() {
|
|||||||
textContentType='URL'
|
textContentType='URL'
|
||||||
onChangeText={(text) => setValue(text)}
|
onChangeText={(text) => setValue(text)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</ListItem>
|
||||||
</DisabledSetting>
|
</ListGroup>
|
||||||
|
|
||||||
<Text className='px-4 text-xs text-neutral-500 mt-1'>
|
<Text className='px-4 text-xs text-neutral-500 mt-1'>
|
||||||
{t("home.settings.plugins.marlin_search.marlin_search_hint")}{" "}
|
{t("home.settings.plugins.marlin_search.marlin_search_hint")}{" "}
|
||||||
<Text className='text-blue-500' onPress={handleOpenLink}>
|
<Text className='text-blue-500' onPress={handleOpenLink}>
|
||||||
{t("home.settings.plugins.marlin_search.read_more_about_marlin")}
|
{t("home.settings.plugins.marlin_search.read_more_about_marlin")}
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</DisabledSetting>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
import { Platform, ScrollView, View } from "react-native";
|
import { useCallback } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Platform, ScrollView, TouchableOpacity, View } from "react-native";
|
||||||
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 { PluginSettings } from "@/components/settings/PluginSettings";
|
import { PluginSettings } from "@/components/settings/PluginSettings";
|
||||||
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
|
|
||||||
export default function PluginsPage() {
|
export default function PluginsPage() {
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { refreshStreamyfinPluginSettings } = useSettings();
|
||||||
|
|
||||||
|
const handleRefreshFromServer = useCallback(async () => {
|
||||||
|
// Returns undefined when the API call fails (handled internally).
|
||||||
|
const refreshed = await refreshStreamyfinPluginSettings();
|
||||||
|
if (refreshed) {
|
||||||
|
toast.success(t("home.settings.plugins.streamystats.toasts.refreshed"));
|
||||||
|
} else {
|
||||||
|
toast.error(
|
||||||
|
t("home.settings.plugins.streamystats.toasts.refresh_failed"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [refreshStreamyfinPluginSettings, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
@@ -18,6 +37,17 @@ export default function PluginsPage() {
|
|||||||
style={{ paddingTop: Platform.OS === "android" ? 10 : 0 }}
|
style={{ paddingTop: Platform.OS === "android" ? 10 : 0 }}
|
||||||
>
|
>
|
||||||
<PluginSettings />
|
<PluginSettings />
|
||||||
|
|
||||||
|
{/* Pulls the centralised Streamyfin plugin settings for every plugin,
|
||||||
|
so it lives on the plugins index rather than inside Streamystats. */}
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={handleRefreshFromServer}
|
||||||
|
className='py-3 rounded-xl bg-neutral-800'
|
||||||
|
>
|
||||||
|
<Text className='text-center text-blue-500'>
|
||||||
|
{t("home.settings.plugins.streamystats.refresh_from_server")}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import { useTranslation } from "react-i18next";
|
|||||||
import {
|
import {
|
||||||
Linking,
|
Linking,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
Switch,
|
|
||||||
TextInput,
|
TextInput,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { toast } from "sonner-native";
|
import { toast } from "sonner-native";
|
||||||
|
import { SettingSwitch } from "@/components/common/SettingSwitch";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { ListGroup } from "@/components/list/ListGroup";
|
import { ListGroup } from "@/components/list/ListGroup";
|
||||||
import { ListItem } from "@/components/list/ListItem";
|
import { ListItem } from "@/components/list/ListItem";
|
||||||
@@ -22,12 +22,7 @@ export default function StreamystatsPage() {
|
|||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
const {
|
const { settings, updateSettings, pluginSettings } = useSettings();
|
||||||
settings,
|
|
||||||
updateSettings,
|
|
||||||
pluginSettings,
|
|
||||||
refreshStreamyfinPluginSettings,
|
|
||||||
} = useSettings();
|
|
||||||
const queryClient = useNetworkAwareQueryClient();
|
const queryClient = useNetworkAwareQueryClient();
|
||||||
|
|
||||||
// Local state for all editable fields
|
// Local state for all editable fields
|
||||||
@@ -49,7 +44,21 @@ export default function StreamystatsPage() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const isUrlLocked = pluginSettings?.streamyStatsServerUrl?.locked === true;
|
const isUrlLocked = pluginSettings?.streamyStatsServerUrl?.locked === true;
|
||||||
const isStreamystatsEnabled = !!url;
|
const searchLocked = pluginSettings?.searchEngine?.locked === true;
|
||||||
|
const movieRecsLocked =
|
||||||
|
pluginSettings?.streamyStatsMovieRecommendations?.locked === true;
|
||||||
|
const seriesRecsLocked =
|
||||||
|
pluginSettings?.streamyStatsSeriesRecommendations?.locked === true;
|
||||||
|
const promotedWatchlistsLocked =
|
||||||
|
pluginSettings?.streamyStatsPromotedWatchlists?.locked === true;
|
||||||
|
const hideWatchlistsTabLocked =
|
||||||
|
pluginSettings?.hideWatchlistsTab?.locked === true;
|
||||||
|
// The input renders the locked admin URL; enablement must follow the same
|
||||||
|
// effective value or every toggle stays disabled until local state syncs.
|
||||||
|
const effectiveUrl = isUrlLocked
|
||||||
|
? (settings?.streamyStatsServerUrl ?? "")
|
||||||
|
: url;
|
||||||
|
const isStreamystatsEnabled = !!effectiveUrl;
|
||||||
|
|
||||||
const onSave = useCallback(() => {
|
const onSave = useCallback(() => {
|
||||||
const cleanUrl = url.endsWith("/") ? url.slice(0, -1) : url;
|
const cleanUrl = url.endsWith("/") ? url.slice(0, -1) : url;
|
||||||
@@ -113,17 +122,6 @@ export default function StreamystatsPage() {
|
|||||||
Linking.openURL("https://github.com/fredrikburmester/streamystats");
|
Linking.openURL("https://github.com/fredrikburmester/streamystats");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefreshFromServer = useCallback(async () => {
|
|
||||||
const newPluginSettings = await refreshStreamyfinPluginSettings();
|
|
||||||
// Update local state with new values
|
|
||||||
const newUrl = newPluginSettings?.streamyStatsServerUrl?.value || "";
|
|
||||||
setUrl(newUrl);
|
|
||||||
if (newUrl) {
|
|
||||||
setUseForSearch(true);
|
|
||||||
}
|
|
||||||
toast.success(t("home.settings.plugins.streamystats.toasts.refreshed"));
|
|
||||||
}, [refreshStreamyfinPluginSettings, t]);
|
|
||||||
|
|
||||||
if (!settings) return null;
|
if (!settings) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -134,7 +132,7 @@ export default function StreamystatsPage() {
|
|||||||
paddingRight: insets.right,
|
paddingRight: insets.right,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View className='px-4'>
|
<View className='px-4 pt-4'>
|
||||||
<ListGroup className='flex-1'>
|
<ListGroup className='flex-1'>
|
||||||
<ListItem
|
<ListItem
|
||||||
title={t("home.settings.plugins.streamystats.url")}
|
title={t("home.settings.plugins.streamystats.url")}
|
||||||
@@ -146,7 +144,7 @@ export default function StreamystatsPage() {
|
|||||||
placeholder={t(
|
placeholder={t(
|
||||||
"home.settings.plugins.streamystats.server_url_placeholder",
|
"home.settings.plugins.streamystats.server_url_placeholder",
|
||||||
)}
|
)}
|
||||||
value={url}
|
value={effectiveUrl}
|
||||||
keyboardType='url'
|
keyboardType='url'
|
||||||
returnKeyType='done'
|
returnKeyType='done'
|
||||||
autoCapitalize='none'
|
autoCapitalize='none'
|
||||||
@@ -171,11 +169,18 @@ export default function StreamystatsPage() {
|
|||||||
>
|
>
|
||||||
<ListItem
|
<ListItem
|
||||||
title={t("home.settings.plugins.streamystats.enable_search")}
|
title={t("home.settings.plugins.streamystats.enable_search")}
|
||||||
disabledByAdmin={pluginSettings?.searchEngine?.locked === true}
|
disabledByAdmin={searchLocked}
|
||||||
>
|
>
|
||||||
<Switch
|
{/* Locked controls show the live admin value and can't be toggled —
|
||||||
value={useForSearch}
|
local form state would let the switch flip while the write guard
|
||||||
disabled={!isStreamystatsEnabled}
|
drops the change. */}
|
||||||
|
<SettingSwitch
|
||||||
|
value={
|
||||||
|
searchLocked
|
||||||
|
? settings?.searchEngine === "Streamystats"
|
||||||
|
: useForSearch
|
||||||
|
}
|
||||||
|
disabled={!isStreamystatsEnabled || searchLocked}
|
||||||
onValueChange={setUseForSearch}
|
onValueChange={setUseForSearch}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@@ -183,52 +188,62 @@ export default function StreamystatsPage() {
|
|||||||
title={t(
|
title={t(
|
||||||
"home.settings.plugins.streamystats.enable_movie_recommendations",
|
"home.settings.plugins.streamystats.enable_movie_recommendations",
|
||||||
)}
|
)}
|
||||||
disabledByAdmin={
|
disabledByAdmin={movieRecsLocked}
|
||||||
pluginSettings?.streamyStatsMovieRecommendations?.locked === true
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={movieRecs}
|
value={
|
||||||
|
movieRecsLocked
|
||||||
|
? (settings?.streamyStatsMovieRecommendations ?? false)
|
||||||
|
: movieRecs
|
||||||
|
}
|
||||||
onValueChange={setMovieRecs}
|
onValueChange={setMovieRecs}
|
||||||
disabled={!isStreamystatsEnabled}
|
disabled={!isStreamystatsEnabled || movieRecsLocked}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem
|
<ListItem
|
||||||
title={t(
|
title={t(
|
||||||
"home.settings.plugins.streamystats.enable_series_recommendations",
|
"home.settings.plugins.streamystats.enable_series_recommendations",
|
||||||
)}
|
)}
|
||||||
disabledByAdmin={
|
disabledByAdmin={seriesRecsLocked}
|
||||||
pluginSettings?.streamyStatsSeriesRecommendations?.locked === true
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={seriesRecs}
|
value={
|
||||||
|
seriesRecsLocked
|
||||||
|
? (settings?.streamyStatsSeriesRecommendations ?? false)
|
||||||
|
: seriesRecs
|
||||||
|
}
|
||||||
onValueChange={setSeriesRecs}
|
onValueChange={setSeriesRecs}
|
||||||
disabled={!isStreamystatsEnabled}
|
disabled={!isStreamystatsEnabled || seriesRecsLocked}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem
|
<ListItem
|
||||||
title={t(
|
title={t(
|
||||||
"home.settings.plugins.streamystats.enable_promoted_watchlists",
|
"home.settings.plugins.streamystats.enable_promoted_watchlists",
|
||||||
)}
|
)}
|
||||||
disabledByAdmin={
|
disabledByAdmin={promotedWatchlistsLocked}
|
||||||
pluginSettings?.streamyStatsPromotedWatchlists?.locked === true
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={promotedWatchlists}
|
value={
|
||||||
|
promotedWatchlistsLocked
|
||||||
|
? (settings?.streamyStatsPromotedWatchlists ?? false)
|
||||||
|
: promotedWatchlists
|
||||||
|
}
|
||||||
onValueChange={setPromotedWatchlists}
|
onValueChange={setPromotedWatchlists}
|
||||||
disabled={!isStreamystatsEnabled}
|
disabled={!isStreamystatsEnabled || promotedWatchlistsLocked}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem
|
<ListItem
|
||||||
title={t("home.settings.plugins.streamystats.hide_watchlists_tab")}
|
title={t("home.settings.plugins.streamystats.hide_watchlists_tab")}
|
||||||
disabledByAdmin={pluginSettings?.hideWatchlistsTab?.locked === true}
|
disabledByAdmin={hideWatchlistsTabLocked}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={hideWatchlistsTab}
|
value={
|
||||||
|
hideWatchlistsTabLocked
|
||||||
|
? (settings?.hideWatchlistsTab ?? false)
|
||||||
|
: hideWatchlistsTab
|
||||||
|
}
|
||||||
onValueChange={setHideWatchlistsTab}
|
onValueChange={setHideWatchlistsTab}
|
||||||
disabled={!isStreamystatsEnabled}
|
disabled={!isStreamystatsEnabled || hideWatchlistsTabLocked}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
@@ -236,15 +251,6 @@ export default function StreamystatsPage() {
|
|||||||
{t("home.settings.plugins.streamystats.home_sections_hint")}
|
{t("home.settings.plugins.streamystats.home_sections_hint")}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={handleRefreshFromServer}
|
|
||||||
className='mt-6 py-3 rounded-xl bg-neutral-800'
|
|
||||||
>
|
|
||||||
<Text className='text-center text-blue-500'>
|
|
||||||
{t("home.settings.plugins.streamystats.refresh_from_server")}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
{/* Disable button - only show if URL is not locked and Streamystats is enabled */}
|
{/* Disable button - only show if URL is not locked and Streamystats is enabled */}
|
||||||
{!isUrlLocked && isStreamystatsEnabled && (
|
{!isUrlLocked && isStreamystatsEnabled && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export default function ArtistsScreen() {
|
|||||||
return (
|
return (
|
||||||
<View className='flex-1 justify-center items-center bg-black px-6'>
|
<View className='flex-1 justify-center items-center bg-black px-6'>
|
||||||
<Text className='text-neutral-500 text-center'>
|
<Text className='text-neutral-500 text-center'>
|
||||||
Missing music library id.
|
{t("music.missing_library_id")}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export default function PlaylistsScreen() {
|
|||||||
return (
|
return (
|
||||||
<View className='flex-1 justify-center items-center bg-black px-6'>
|
<View className='flex-1 justify-center items-center bg-black px-6'>
|
||||||
<Text className='text-neutral-500 text-center'>
|
<Text className='text-neutral-500 text-center'>
|
||||||
Missing music library id.
|
{t("music.missing_library_id")}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ export default function SuggestionsScreen() {
|
|||||||
return (
|
return (
|
||||||
<View className='flex-1 justify-center items-center bg-black px-6'>
|
<View className='flex-1 justify-center items-center bg-black px-6'>
|
||||||
<Text className='text-neutral-500 text-center'>
|
<Text className='text-neutral-500 text-center'>
|
||||||
Missing music library id.
|
{t("music.missing_library_id")}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
@@ -72,6 +73,7 @@ const ARTWORK_SIZE = SCREEN_WIDTH - 80;
|
|||||||
type ViewMode = "player" | "queue";
|
type ViewMode = "player" | "queue";
|
||||||
|
|
||||||
export default function NowPlayingScreen() {
|
export default function NowPlayingScreen() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [api] = useAtom(apiAtom);
|
const [api] = useAtom(apiAtom);
|
||||||
const [user] = useAtom(userAtom);
|
const [user] = useAtom(userAtom);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -230,7 +232,9 @@ export default function NowPlayingScreen() {
|
|||||||
paddingBottom: Platform.OS === "android" ? insets.bottom : 0,
|
paddingBottom: Platform.OS === "android" ? insets.bottom : 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text className='text-neutral-500'>No track playing</Text>
|
<Text className='text-neutral-500'>
|
||||||
|
{t("music.no_track_playing")}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</BottomSheetModalProvider>
|
</BottomSheetModalProvider>
|
||||||
);
|
);
|
||||||
@@ -267,7 +271,7 @@ export default function NowPlayingScreen() {
|
|||||||
: "text-neutral-500"
|
: "text-neutral-500"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Now Playing
|
{t("music.now_playing")}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -718,6 +722,7 @@ const QueueView: React.FC<QueueViewProps> = ({
|
|||||||
onRemoveFromQueue,
|
onRemoveFromQueue,
|
||||||
onReorderQueue,
|
onReorderQueue,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const renderQueueItem = useCallback(
|
const renderQueueItem = useCallback(
|
||||||
({ item, drag, isActive, getIndex }: RenderItemParams<BaseItemDto>) => {
|
({ item, drag, isActive, getIndex }: RenderItemParams<BaseItemDto>) => {
|
||||||
const index = getIndex() ?? 0;
|
const index = getIndex() ?? 0;
|
||||||
@@ -831,13 +836,15 @@ const QueueView: React.FC<QueueViewProps> = ({
|
|||||||
ListHeaderComponent={
|
ListHeaderComponent={
|
||||||
<View className='px-4 py-2'>
|
<View className='px-4 py-2'>
|
||||||
<Text className='text-neutral-400 text-xs uppercase tracking-wider'>
|
<Text className='text-neutral-400 text-xs uppercase tracking-wider'>
|
||||||
{history.length > 0 ? "Playing from queue" : "Up next"}
|
{history.length > 0
|
||||||
|
? t("music.playing_from_queue")
|
||||||
|
: t("music.up_next")}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
<View className='flex-1 items-center justify-center py-20'>
|
<View className='flex-1 items-center justify-center py-20'>
|
||||||
<Text className='text-neutral-500'>Queue is empty</Text>
|
<Text className='text-neutral-500'>{t("music.queue_empty")}</Text>
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ const SubtitleResultCard = React.forwardRef<
|
|||||||
>(({ result, hasTVPreferredFocus, isDownloading, onPress }, ref) => {
|
>(({ result, hasTVPreferredFocus, isDownloading, onPress }, ref) => {
|
||||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||||
useTVFocusAnimation({ scaleAmount: 1.03 });
|
useTVFocusAnimation({ scaleAmount: 1.03 });
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
@@ -328,7 +329,7 @@ const SubtitleResultCard = React.forwardRef<
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Text style={[styles.flagText, { fontSize: scaleSize(10) }]}>
|
<Text style={[styles.flagText, { fontSize: scaleSize(10) }]}>
|
||||||
Hash Match
|
{t("player.hash_match")}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import { Link, Stack } from "expo-router";
|
import { Link, Stack } from "expo-router";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { StyleSheet } from "react-native";
|
import { StyleSheet } from "react-native";
|
||||||
|
|
||||||
import { ThemedText } from "@/components/ThemedText";
|
import { ThemedText } from "@/components/ThemedText";
|
||||||
import { ThemedView } from "@/components/ThemedView";
|
import { ThemedView } from "@/components/ThemedView";
|
||||||
|
|
||||||
export default function NotFoundScreen() {
|
export default function NotFoundScreen() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack.Screen options={{ title: "Oops!" }} />
|
<Stack.Screen options={{ title: t("home.oops") }} />
|
||||||
<ThemedView style={styles.container}>
|
<ThemedView style={styles.container}>
|
||||||
<ThemedText type='title'>This screen doesn't exist.</ThemedText>
|
<ThemedText type='title'>{t("not_found.title")}</ThemedText>
|
||||||
<Link href={"/home"} style={styles.link}>
|
<Link href={"/home"} style={styles.link}>
|
||||||
<ThemedText type='link'>Go to home screen!</ThemedText>
|
<ThemedText type='link'>{t("not_found.go_home")}</ThemedText>
|
||||||
</Link>
|
</Link>
|
||||||
</ThemedView>
|
</ThemedView>
|
||||||
</>
|
</>
|
||||||
|
|||||||
278
app/_layout.tsx
278
app/_layout.tsx
@@ -11,6 +11,7 @@ import { Image } from "expo-image";
|
|||||||
import { DarkTheme, ThemeProvider } from "expo-router/react-navigation";
|
import { DarkTheme, ThemeProvider } from "expo-router/react-navigation";
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { GlobalModal } from "@/components/GlobalModal";
|
import { GlobalModal } from "@/components/GlobalModal";
|
||||||
|
import { PendingAccountSaveModal } from "@/components/PendingAccountSaveModal";
|
||||||
import { enableTVMenuKeyInterception } from "@/hooks/useTVBackHandler";
|
import { enableTVMenuKeyInterception } from "@/hooks/useTVBackHandler";
|
||||||
import i18n from "@/i18n";
|
import i18n from "@/i18n";
|
||||||
import { DownloadProvider } from "@/providers/DownloadProvider";
|
import { DownloadProvider } from "@/providers/DownloadProvider";
|
||||||
@@ -27,6 +28,7 @@ import { NetworkStatusProvider } from "@/providers/NetworkStatusProvider";
|
|||||||
import { PlaySettingsProvider } from "@/providers/PlaySettingsProvider";
|
import { PlaySettingsProvider } from "@/providers/PlaySettingsProvider";
|
||||||
import { ServerUrlProvider } from "@/providers/ServerUrlProvider";
|
import { ServerUrlProvider } from "@/providers/ServerUrlProvider";
|
||||||
import { WebSocketProvider } from "@/providers/WebSocketProvider";
|
import { WebSocketProvider } from "@/providers/WebSocketProvider";
|
||||||
|
import { WifiSsidProvider } from "@/providers/WifiSsidProvider";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
import {
|
import {
|
||||||
BACKGROUND_FETCH_TASK,
|
BACKGROUND_FETCH_TASK,
|
||||||
@@ -425,143 +427,149 @@ function Layout() {
|
|||||||
>
|
>
|
||||||
<JellyfinProvider>
|
<JellyfinProvider>
|
||||||
<InactivityProvider>
|
<InactivityProvider>
|
||||||
<ServerUrlProvider>
|
<WifiSsidProvider>
|
||||||
<NetworkStatusProvider>
|
<ServerUrlProvider>
|
||||||
<PlaySettingsProvider>
|
<NetworkStatusProvider>
|
||||||
<LogProvider>
|
<PlaySettingsProvider>
|
||||||
<WebSocketProvider>
|
<LogProvider>
|
||||||
<DownloadProvider>
|
<WebSocketProvider>
|
||||||
<MusicPlayerProvider>
|
<DownloadProvider>
|
||||||
<GlobalModalProvider>
|
<MusicPlayerProvider>
|
||||||
<BottomSheetModalProvider>
|
<GlobalModalProvider>
|
||||||
<IntroSheetProvider>
|
<BottomSheetModalProvider>
|
||||||
<ThemeProvider value={DarkTheme}>
|
<IntroSheetProvider>
|
||||||
<SystemBars style='light' hidden={false} />
|
<ThemeProvider value={DarkTheme}>
|
||||||
<Stack initialRouteName='(auth)/(tabs)'>
|
<SystemBars style='light' hidden={false} />
|
||||||
<Stack.Screen
|
<Stack initialRouteName='(auth)/(tabs)'>
|
||||||
name='(auth)/(tabs)'
|
<Stack.Screen
|
||||||
options={{
|
name='(auth)/(tabs)'
|
||||||
headerShown: false,
|
options={{
|
||||||
title: "",
|
headerShown: false,
|
||||||
header: () => null,
|
title: "",
|
||||||
|
header: () => null,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='(auth)/player'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
title: "",
|
||||||
|
header: () => null,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='(auth)/now-playing'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "modal",
|
||||||
|
gestureEnabled: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='login'
|
||||||
|
options={{
|
||||||
|
headerShown: true,
|
||||||
|
title: "",
|
||||||
|
headerTransparent:
|
||||||
|
Platform.OS === "ios",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen name='+not-found' />
|
||||||
|
<Stack.Screen
|
||||||
|
name='(auth)/tv-option-modal'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "transparentModal",
|
||||||
|
animation: "fade",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='(auth)/tv-subtitle-modal'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "transparentModal",
|
||||||
|
animation: "fade",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='(auth)/tv-request-modal'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "transparentModal",
|
||||||
|
animation: "fade",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='(auth)/tv-season-select-modal'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "transparentModal",
|
||||||
|
animation: "fade",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='(auth)/tv-series-season-modal'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "transparentModal",
|
||||||
|
animation: "fade",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='tv-account-action-modal'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "transparentModal",
|
||||||
|
animation: "fade",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='tv-account-select-modal'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "transparentModal",
|
||||||
|
animation: "fade",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='(auth)/tv-user-switch-modal'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "transparentModal",
|
||||||
|
animation: "fade",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Toaster
|
||||||
|
duration={4000}
|
||||||
|
toastOptions={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: "#262626",
|
||||||
|
borderColor: "#363639",
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
titleStyle: {
|
||||||
|
color: "white",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
|
closeButton
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
{!Platform.isTV && <GlobalModal />}
|
||||||
name='(auth)/player'
|
{!Platform.isTV && (
|
||||||
options={{
|
<PendingAccountSaveModal />
|
||||||
headerShown: false,
|
)}
|
||||||
title: "",
|
</ThemeProvider>
|
||||||
header: () => null,
|
</IntroSheetProvider>
|
||||||
}}
|
</BottomSheetModalProvider>
|
||||||
/>
|
</GlobalModalProvider>
|
||||||
<Stack.Screen
|
</MusicPlayerProvider>
|
||||||
name='(auth)/now-playing'
|
</DownloadProvider>
|
||||||
options={{
|
</WebSocketProvider>
|
||||||
headerShown: false,
|
</LogProvider>
|
||||||
presentation: "modal",
|
</PlaySettingsProvider>
|
||||||
gestureEnabled: true,
|
</NetworkStatusProvider>
|
||||||
}}
|
</ServerUrlProvider>
|
||||||
/>
|
</WifiSsidProvider>
|
||||||
<Stack.Screen
|
|
||||||
name='login'
|
|
||||||
options={{
|
|
||||||
headerShown: true,
|
|
||||||
title: "",
|
|
||||||
headerTransparent: Platform.OS === "ios",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen name='+not-found' />
|
|
||||||
<Stack.Screen
|
|
||||||
name='(auth)/tv-option-modal'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "transparentModal",
|
|
||||||
animation: "fade",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='(auth)/tv-subtitle-modal'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "transparentModal",
|
|
||||||
animation: "fade",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='(auth)/tv-request-modal'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "transparentModal",
|
|
||||||
animation: "fade",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='(auth)/tv-season-select-modal'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "transparentModal",
|
|
||||||
animation: "fade",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='(auth)/tv-series-season-modal'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "transparentModal",
|
|
||||||
animation: "fade",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='tv-account-action-modal'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "transparentModal",
|
|
||||||
animation: "fade",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='tv-account-select-modal'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "transparentModal",
|
|
||||||
animation: "fade",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='(auth)/tv-user-switch-modal'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "transparentModal",
|
|
||||||
animation: "fade",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
<Toaster
|
|
||||||
duration={4000}
|
|
||||||
toastOptions={{
|
|
||||||
style: {
|
|
||||||
backgroundColor: "#262626",
|
|
||||||
borderColor: "#363639",
|
|
||||||
borderWidth: 1,
|
|
||||||
},
|
|
||||||
titleStyle: {
|
|
||||||
color: "white",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
closeButton
|
|
||||||
/>
|
|
||||||
{!Platform.isTV && <GlobalModal />}
|
|
||||||
</ThemeProvider>
|
|
||||||
</IntroSheetProvider>
|
|
||||||
</BottomSheetModalProvider>
|
|
||||||
</GlobalModalProvider>
|
|
||||||
</MusicPlayerProvider>
|
|
||||||
</DownloadProvider>
|
|
||||||
</WebSocketProvider>
|
|
||||||
</LogProvider>
|
|
||||||
</PlaySettingsProvider>
|
|
||||||
</NetworkStatusProvider>
|
|
||||||
</ServerUrlProvider>
|
|
||||||
</InactivityProvider>
|
</InactivityProvider>
|
||||||
</JellyfinProvider>
|
</JellyfinProvider>
|
||||||
</PersistQueryClientProvider>
|
</PersistQueryClientProvider>
|
||||||
|
|||||||
264
bun.lock
264
bun.lock
@@ -5,7 +5,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "streamyfin",
|
"name": "streamyfin",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bottom-tabs/react-navigation": "1.2.0",
|
"@bottom-tabs/react-navigation": "1.4.0",
|
||||||
"@douglowder/expo-av-route-picker-view": "^0.0.5",
|
"@douglowder/expo-av-route-picker-view": "^0.0.5",
|
||||||
"@expo/metro-runtime": "~56.0.15",
|
"@expo/metro-runtime": "~56.0.15",
|
||||||
"@expo/react-native-action-sheet": "^4.1.1",
|
"@expo/react-native-action-sheet": "^4.1.1",
|
||||||
@@ -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",
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
"expo-font": "~56.0.6",
|
"expo-font": "~56.0.6",
|
||||||
"expo-haptics": "~56.0.3",
|
"expo-haptics": "~56.0.3",
|
||||||
"expo-image": "~56.0.11",
|
"expo-image": "~56.0.11",
|
||||||
|
"expo-key-event": "^1.9.0",
|
||||||
"expo-linear-gradient": "~56.0.4",
|
"expo-linear-gradient": "~56.0.4",
|
||||||
"expo-linking": "~56.0.14",
|
"expo-linking": "~56.0.14",
|
||||||
"expo-localization": "~56.0.6",
|
"expo-localization": "~56.0.6",
|
||||||
@@ -62,7 +64,7 @@
|
|||||||
"react-i18next": "17.0.8",
|
"react-i18next": "17.0.8",
|
||||||
"react-native": "npm:react-native-tvos@0.85.3-0",
|
"react-native": "npm:react-native-tvos@0.85.3-0",
|
||||||
"react-native-awesome-slider": "^2.9.0",
|
"react-native-awesome-slider": "^2.9.0",
|
||||||
"react-native-bottom-tabs": "1.2.0",
|
"react-native-bottom-tabs": "1.4.0",
|
||||||
"react-native-circular-progress": "^1.4.1",
|
"react-native-circular-progress": "^1.4.1",
|
||||||
"react-native-collapsible": "^1.6.2",
|
"react-native-collapsible": "^1.6.2",
|
||||||
"react-native-country-flag": "^2.0.2",
|
"react-native-country-flag": "^2.0.2",
|
||||||
@@ -92,7 +94,7 @@
|
|||||||
"react-native-uuid": "^2.0.3",
|
"react-native-uuid": "^2.0.3",
|
||||||
"react-native-volume-manager": "^2.0.8",
|
"react-native-volume-manager": "^2.0.8",
|
||||||
"react-native-web": "^0.21.0",
|
"react-native-web": "^0.21.0",
|
||||||
"react-native-worklets": "0.11.0",
|
"react-native-worklets": "0.8.3",
|
||||||
"sonner-native": "0.21.2",
|
"sonner-native": "0.21.2",
|
||||||
"tailwindcss": "3.3.2",
|
"tailwindcss": "3.3.2",
|
||||||
"use-debounce": "^10.0.4",
|
"use-debounce": "^10.0.4",
|
||||||
@@ -285,7 +287,7 @@
|
|||||||
|
|
||||||
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.16", "", { "os": "win32", "cpu": "x64" }, "sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw=="],
|
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.16", "", { "os": "win32", "cpu": "x64" }, "sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw=="],
|
||||||
|
|
||||||
"@bottom-tabs/react-navigation": ["@bottom-tabs/react-navigation@1.2.0", "", { "dependencies": { "color": "^5.0.0" }, "peerDependencies": { "@react-navigation/native": ">=7", "react": "*", "react-native": "*", "react-native-bottom-tabs": "*" } }, "sha512-gEnLP7q9Iai0KlVxHDIdlrDgkvJ5vwPzL2+2ucz5BdPWd++Cf5GO1jPq92R4/85PrioviCZnlAD91Wx8WxPOjA=="],
|
"@bottom-tabs/react-navigation": ["@bottom-tabs/react-navigation@1.4.0", "", { "dependencies": { "color": "^5.0.0" }, "peerDependencies": { "@react-navigation/native": ">=7", "react": "*", "react-native": "*", "react-native-bottom-tabs": "*" } }, "sha512-EeOCrecZ4YiVmFASKtoLyQFFFBjbmkV6FBD4Oxyjs9aSexuX4jYbC8yapfnevqyMnYINz0qYTvpUEILciNL+nQ=="],
|
||||||
|
|
||||||
"@dominicstop/ts-event-emitter": ["@dominicstop/ts-event-emitter@1.1.0", "", {}, "sha512-CcxmJIvUb1vsFheuGGVSQf4KdPZC44XolpUT34+vlal+LyQoBUOn31pjFET5M9ctOxEpt8xa0M3/2M7uUiAoJw=="],
|
"@dominicstop/ts-event-emitter": ["@dominicstop/ts-event-emitter@1.1.0", "", {}, "sha512-CcxmJIvUb1vsFheuGGVSQf4KdPZC44XolpUT34+vlal+LyQoBUOn31pjFET5M9ctOxEpt8xa0M3/2M7uUiAoJw=="],
|
||||||
|
|
||||||
@@ -347,63 +349,63 @@
|
|||||||
|
|
||||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
|
||||||
|
|
||||||
"@expo-google-fonts/material-symbols": ["@expo-google-fonts/material-symbols@0.4.38", "", {}, "sha512-IJkBtN1o8u9BW5fvSii1MyHPQ7Q0HxbWcVBvOrOzgMLpVtZw7R2w94wBTVR7kZwv3w1JNTESMmLA5Sqn1+Z36A=="],
|
"@expo-google-fonts/material-symbols": ["@expo-google-fonts/material-symbols@0.4.41", "", {}, "sha512-BKNSJ6oFGWi+aXu6PF+1EdI43J8Ag+Sm7Ste1SXYmC2eYOJasyjXHkKjgkIieBB1eDZQJSPWuPv421Vg6N5tCQ=="],
|
||||||
|
|
||||||
"@expo/cli": ["@expo/cli@56.1.16", "", { "dependencies": { "@expo/code-signing-certificates": "^0.0.6", "@expo/config": "~56.0.9", "@expo/config-plugins": "~56.0.9", "@expo/devcert": "^1.2.1", "@expo/env": "~2.3.0", "@expo/image-utils": "^0.10.1", "@expo/inline-modules": "^0.0.12", "@expo/json-file": "^10.2.0", "@expo/log-box": "^56.0.13", "@expo/metro": "~56.0.0", "@expo/metro-config": "~56.0.14", "@expo/metro-file-map": "^56.0.3", "@expo/osascript": "^2.6.0", "@expo/package-manager": "^1.12.1", "@expo/plist": "^0.7.0", "@expo/prebuild-config": "^56.0.16", "@expo/require-utils": "^56.1.3", "@expo/router-server": "^56.0.14", "@expo/schema-utils": "^56.0.0", "@expo/spawn-async": "^1.8.0", "@expo/ws-tunnel": "^2.0.0", "@expo/xcpretty": "^4.4.4", "@react-native/dev-middleware": "0.85.3", "accepts": "^1.3.8", "arg": "^5.0.2", "bplist-creator": "0.1.0", "bplist-parser": "^0.3.1", "chalk": "^4.0.0", "ci-info": "^3.3.0", "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", "dnssd-advertise": "^1.1.4", "expo-server": "^56.0.5", "fetch-nodeshim": "^0.4.10", "getenv": "^2.0.0", "glob": "^13.0.0", "lan-network": "^0.2.1", "multitars": "^1.0.0", "node-forge": "^1.3.3", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "picomatch": "^4.0.4", "pretty-format": "^29.7.0", "progress": "^2.0.3", "prompts": "^2.3.2", "resolve-from": "^5.0.0", "semver": "^7.6.0", "send": "^0.19.0", "slugify": "^1.3.4", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "terminal-link": "^2.1.1", "toqr": "^0.1.1", "wrap-ansi": "^7.0.0", "ws": "^8.12.1", "zod": "^3.25.76" }, "peerDependencies": { "expo": "*", "expo-router": "*", "react-native": "*" }, "optionalPeers": ["expo-router", "react-native"], "bin": { "expo-internal": "main.js" } }, "sha512-VBQn0mqAwc67b9Cn0RVXyeodghomAx5xGRhA/bXaQzuxDjMQk0zIOb6pXMZX7yiIwJW66UZt/zQiJNSv6aWJYw=="],
|
"@expo/cli": ["@expo/cli@56.1.20", "", { "dependencies": { "@expo/code-signing-certificates": "^0.0.6", "@expo/config": "~56.0.12", "@expo/config-plugins": "~56.0.13", "@expo/devcert": "^1.2.1", "@expo/env": "~2.3.1", "@expo/image-utils": "^0.10.3", "@expo/inline-modules": "^0.0.14", "@expo/json-file": "^10.2.0", "@expo/log-box": "^56.0.14", "@expo/metro": "~56.0.0", "@expo/metro-config": "~56.0.17", "@expo/metro-file-map": "^56.0.3", "@expo/osascript": "^2.6.0", "@expo/package-manager": "^1.12.1", "@expo/plist": "^0.7.0", "@expo/prebuild-config": "^56.0.20", "@expo/require-utils": "^56.1.5", "@expo/router-server": "^56.0.16", "@expo/schema-utils": "^56.0.2", "@expo/spawn-async": "^1.8.0", "@expo/ws-tunnel": "^2.0.0", "@expo/xcpretty": "^4.4.4", "@react-native/dev-middleware": "0.85.3", "accepts": "^1.3.8", "agent-cli-detector": "^0.1.2", "arg": "^5.0.2", "bplist-creator": "0.1.0", "bplist-parser": "^0.3.1", "chalk": "^4.0.0", "ci-info": "^3.3.0", "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", "dnssd-advertise": "^1.1.6", "expo-server": "^56.0.5", "fetch-nodeshim": "^0.4.10", "getenv": "^2.0.0", "glob": "^13.0.0", "lan-network": "^0.2.1", "multitars": "^1.0.0", "node-forge": "^1.3.3", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "picomatch": "^4.0.4", "pretty-format": "^29.7.0", "progress": "^2.0.3", "prompts": "^2.3.2", "resolve-from": "^5.0.0", "semver": "^7.6.0", "send": "^0.19.0", "slugify": "^1.3.4", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "terminal-link": "^2.1.1", "toqr": "^0.1.1", "wrap-ansi": "^7.0.0", "ws": "^8.12.1", "zod": "^3.25.76" }, "peerDependencies": { "expo": "*", "expo-router": "*", "react-native": "*" }, "optionalPeers": ["expo-router", "react-native"], "bin": { "expo-internal": "main.js" } }, "sha512-xWkKvOBkOE3G/PpU29fjmAjKpCIqTIVSxG8oA+E6pYUElcPZ9gRofAdTE321lUoJVHwEjKmpn93EHqsajItDTQ=="],
|
||||||
|
|
||||||
"@expo/code-signing-certificates": ["@expo/code-signing-certificates@0.0.6", "", { "dependencies": { "node-forge": "^1.3.3" } }, "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w=="],
|
"@expo/code-signing-certificates": ["@expo/code-signing-certificates@0.0.6", "", { "dependencies": { "node-forge": "^1.3.3" } }, "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w=="],
|
||||||
|
|
||||||
"@expo/config": ["@expo/config@56.0.9", "", { "dependencies": { "@expo/config-plugins": "~56.0.8", "@expo/config-types": "^56.0.5", "@expo/json-file": "^10.2.0", "@expo/require-utils": "^56.1.3", "deepmerge": "^4.3.1", "getenv": "^2.0.0", "glob": "^13.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4" } }, "sha512-/lqFeWGSrhpKJVP8tTN8LjuoIe8u8q2w7FzBL0C+wHgl+WM8l1qUIEYWy/sMvsG/NbpUIUsDHJRhQvOkU58eIw=="],
|
"@expo/config": ["@expo/config@56.0.12", "", { "dependencies": { "@expo/config-plugins": "~56.0.13", "@expo/config-types": "^56.0.7", "@expo/json-file": "^10.2.0", "@expo/require-utils": "^56.1.5", "deepmerge": "^4.3.1", "getenv": "^2.0.0", "glob": "^13.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4" } }, "sha512-8X+FtflatWdFqBFlPxtyNRaj2dzMCPzi5wImO8IbhOLRKQVWyXdoLb26j0WjpWZaN1qo3Kf90t5yIcWdbbnfVA=="],
|
||||||
|
|
||||||
"@expo/config-plugins": ["@expo/config-plugins@56.0.9", "", { "dependencies": { "@expo/config-types": "^56.0.6", "@expo/json-file": "~10.2.0", "@expo/plist": "^0.7.0", "@expo/require-utils": "^56.1.3", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^13.0.0", "semver": "^7.5.4", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-/6a/S9USwx8OC9tGjHxbviLFiBHyueN3aoNWMLvWDEJoZ1CIVW800ZBzwXq/FYNK2qzcN1LxFmQtzD1zeFQKNA=="],
|
"@expo/config-plugins": ["@expo/config-plugins@56.0.13", "", { "dependencies": { "@expo/config-types": "^56.0.7", "@expo/json-file": "~10.2.0", "@expo/plist": "^0.7.0", "@expo/require-utils": "^56.1.5", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^13.0.0", "semver": "^7.5.4", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-BWtRkvUKLb4xaMLTRXmrHPHl+RI5nKF2MKJEXODHmLtVgUlXOz66XSCU4bEvjfr8yYTx+8DQ0JO54Y7Y0kwokA=="],
|
||||||
|
|
||||||
"@expo/config-types": ["@expo/config-types@56.0.6", "", {}, "sha512-4Y6Aum5J4Re5NnxGVofRNe1aDwUBOmWhQYkynZsqzRtX/zEA1ADUeyHXuEckv9YD9djiyT7bKtLt5gKL3mA6VQ=="],
|
"@expo/config-types": ["@expo/config-types@56.0.7", "", {}, "sha512-V7bxawNsNned/yMppAHdisIOxniZXgPKRWpIUiQOQBs45/A5MBd2gfDM4Ecq5gnbilnQUTaI6Zxn6JcW7L3TAA=="],
|
||||||
|
|
||||||
"@expo/devcert": ["@expo/devcert@1.2.1", "", { "dependencies": { "@expo/sudo-prompt": "^9.3.1", "debug": "^3.1.0" } }, "sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA=="],
|
"@expo/devcert": ["@expo/devcert@1.2.1", "", { "dependencies": { "@expo/sudo-prompt": "^9.3.1", "debug": "^3.1.0" } }, "sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA=="],
|
||||||
|
|
||||||
"@expo/devtools": ["@expo/devtools@56.0.2", "", { "dependencies": { "chalk": "^4.1.2" }, "peerDependencies": { "react": "*", "react-native": "*" }, "optionalPeers": ["react", "react-native"] }, "sha512-ANl4kPdbe0/HQYWkDEN79S6bQhI+i/ZCnPxuC853pPsB4svhINC7Ku9lmGOKPsUUWWnrHg1spkDGQBZ4sD6JxQ=="],
|
"@expo/devtools": ["@expo/devtools@56.0.2", "", { "dependencies": { "chalk": "^4.1.2" }, "peerDependencies": { "react": "*", "react-native": "*" }, "optionalPeers": ["react", "react-native"] }, "sha512-ANl4kPdbe0/HQYWkDEN79S6bQhI+i/ZCnPxuC853pPsB4svhINC7Ku9lmGOKPsUUWWnrHg1spkDGQBZ4sD6JxQ=="],
|
||||||
|
|
||||||
"@expo/dom-webview": ["@expo/dom-webview@56.0.5", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-UIEJxkLg6cHqofKrpWpkn9E6ApxVRtCgZhZkARPr9VV7rBVloJgeroTHs31YgU/JpbI5lLQOnfOlGo54W6C2Ew=="],
|
"@expo/dom-webview": ["@expo/dom-webview@56.0.6", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-DQY3Tj5nrPbpIfEQiD9xbyEFTu25yEORa02Eyzl5rXszDzxDT4VkZDHkyErNQOyE1qjublJHNFhE5bDm4tRfqA=="],
|
||||||
|
|
||||||
"@expo/env": ["@expo/env@2.3.0", "", { "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", "getenv": "^2.0.0" } }, "sha512-9HnnIbzwTTdbwSjNLXTk0fPm9ZwMJ7c1/31tsni8HZ8Q62KzYCyspahH+V365vg5J6lr001DzNwBxVWSaYCQLg=="],
|
"@expo/env": ["@expo/env@2.3.1", "", { "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", "getenv": "^2.0.0" } }, "sha512-JvBdZa5OkTd+dGEtt3fLW9OF6RlIp9SNu9VyMSiWPV5szMDTSAYbX8Uj3cRvZcU1zzaRbqnTSsHk2AE8WXHfxQ=="],
|
||||||
|
|
||||||
"@expo/expo-modules-macros-plugin": ["@expo/expo-modules-macros-plugin@0.2.2", "", {}, "sha512-4IMzPDIo/VOXREQjsJtliSfqYVZvfzU2SLFS/9sKMWF848S8CHx+e/E+Vf0TcMvpWCCKX5umyqxb13KJJ+YUzg=="],
|
"@expo/expo-modules-macros-plugin": ["@expo/expo-modules-macros-plugin@0.2.2", "", {}, "sha512-4IMzPDIo/VOXREQjsJtliSfqYVZvfzU2SLFS/9sKMWF848S8CHx+e/E+Vf0TcMvpWCCKX5umyqxb13KJJ+YUzg=="],
|
||||||
|
|
||||||
"@expo/fingerprint": ["@expo/fingerprint@0.19.4", "", { "dependencies": { "@expo/env": "^2.3.0", "@expo/spawn-async": "^1.8.0", "arg": "^5.0.2", "chalk": "^4.1.2", "debug": "^4.3.4", "getenv": "^2.0.0", "glob": "^13.0.0", "ignore": "^5.3.1", "minimatch": "^10.2.2", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, "bin": { "fingerprint": "bin/cli.js" } }, "sha512-PsowRlO8+S7JlO8go7yhNEXp7sqlsWDE2AlCwoss7zH0dcajXFo74Fy0KdXEc4UXK7kKoHD37oDgsZ8aHSLr7A=="],
|
"@expo/fingerprint": ["@expo/fingerprint@0.19.8", "", { "dependencies": { "@expo/env": "^2.3.1", "@expo/spawn-async": "^1.8.0", "arg": "^5.0.2", "chalk": "^4.1.2", "debug": "^4.3.4", "getenv": "^2.0.0", "glob": "^13.0.0", "ignore": "^5.3.1", "minimatch": "^10.2.2", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, "bin": { "fingerprint": "bin/cli.js" } }, "sha512-zM0UMJLxYJnYq0+IzASGUYwQulENnC4qmXCAwJYmjD66AG+qTfyab4yTmL2gbiWyBAsWv0vPtP+ahEhtsT2CjQ=="],
|
||||||
|
|
||||||
"@expo/image-utils": ["@expo/image-utils@0.10.1", "", { "dependencies": { "@expo/require-utils": "^56.1.3", "@expo/spawn-async": "^1.8.0", "chalk": "^4.0.0", "getenv": "^2.0.0", "jimp-compact": "0.16.1", "parse-png": "^2.1.0", "semver": "^7.6.0" } }, "sha512-YDeefvmYdihS7Wp3ESDUVnOgOSWmj2Cczm9lVNDdm4MqQLdAKm/LPYg83HtFQPfefRlAxyHrQR/O9kIXN9C1Wg=="],
|
"@expo/image-utils": ["@expo/image-utils@0.10.3", "", { "dependencies": { "@expo/require-utils": "^56.1.5", "@expo/spawn-async": "^1.8.0", "chalk": "^4.0.0", "getenv": "^2.0.0", "jimp-compact": "0.16.1", "parse-png": "^2.1.0", "semver": "^7.6.0" } }, "sha512-FgVp21Q05GRi08XwkzdZbBb/sBgCcP66snxlyN/HyisdtTcSbTr1qV5wkXYjq+6Q1xllXiKLmCwjDvydoN2vyg=="],
|
||||||
|
|
||||||
"@expo/inline-modules": ["@expo/inline-modules@0.0.12", "", { "dependencies": { "@expo/config-plugins": "~56.0.9" } }, "sha512-SNIZr/HWfIQPTZBwmukItxpc7ws1SgMUywYq1dnQvDknQDjJcuWAasIRFUjsK15yQ1xb4G5CP7VHtbN3V4lENg=="],
|
"@expo/inline-modules": ["@expo/inline-modules@0.0.14", "", { "dependencies": { "@expo/config-plugins": "~56.0.13" } }, "sha512-jHrqsgpb5QnnwGfxD6NrUAMW9JcS1+j/e8GiLSy+12Md7aRnEWqi347xZ8Q57tkow1iXdeVLXPeyn1VmmCB8fA=="],
|
||||||
|
|
||||||
"@expo/json-file": ["@expo/json-file@10.2.0", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "json5": "^2.2.3" } }, "sha512-S6XzKe3R9GQeHiUPXc3xJjOv2VJhOEwFYf7xdC2z2cUqt3kZJ9mSO877sNQloVdnW/SUCtPY3bexlM7nwq+CAQ=="],
|
"@expo/json-file": ["@expo/json-file@10.2.0", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "json5": "^2.2.3" } }, "sha512-S6XzKe3R9GQeHiUPXc3xJjOv2VJhOEwFYf7xdC2z2cUqt3kZJ9mSO877sNQloVdnW/SUCtPY3bexlM7nwq+CAQ=="],
|
||||||
|
|
||||||
"@expo/local-build-cache-provider": ["@expo/local-build-cache-provider@56.0.8", "", { "dependencies": { "@expo/config": "~56.0.9", "chalk": "^4.1.2" } }, "sha512-UsuXwpNi57MNhzZ3be4XThc8xW6nzk3Wu37s1+2qcfZGeJcMLKDFfwO6n8YXeIiGlCsOi0Ee1rsTdgjrKt/YJQ=="],
|
"@expo/local-build-cache-provider": ["@expo/local-build-cache-provider@56.0.10", "", { "dependencies": { "@expo/config": "~56.0.12", "chalk": "^4.1.2" } }, "sha512-ki4bnpcWaZRfOdcN1TYLuN02k+R8UaUJ9rQuSOKlj471ZnDcrAn/muugA8xHiIbZ/hS5HGNIy5z5A/unTPjVUw=="],
|
||||||
|
|
||||||
"@expo/log-box": ["@expo/log-box@56.0.13", "", { "dependencies": { "@expo/dom-webview": "^56.0.5", "anser": "^1.4.9", "stacktrace-parser": "^0.1.10" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-QWRZSpWPyjkDLVQio4R7oAzg/Av2MOt/DciFkfjr8qQ3qxGVn1Rt1oHP/80hvcWDcHFV7N6PqpyxRXw6nbxzKQ=="],
|
"@expo/log-box": ["@expo/log-box@56.0.14", "", { "dependencies": { "@expo/dom-webview": "^56.0.6", "anser": "^1.4.9", "stacktrace-parser": "^0.1.10" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-3Eadcxz0J2NESG2iKe8DnAggsRCWqY0mBLhgQPNzClPVjS6o4NyD0F8kuOvWNngFwpJBC08HhY2o8P1mgSgeJg=="],
|
||||||
|
|
||||||
"@expo/metro": ["@expo/metro@56.0.0", "", { "dependencies": { "metro": "0.84.4", "metro-babel-transformer": "0.84.4", "metro-cache": "0.84.4", "metro-cache-key": "0.84.4", "metro-config": "0.84.4", "metro-core": "0.84.4", "metro-file-map": "0.84.4", "metro-minify-terser": "0.84.4", "metro-resolver": "0.84.4", "metro-runtime": "0.84.4", "metro-source-map": "0.84.4", "metro-symbolicate": "0.84.4", "metro-transform-plugins": "0.84.4", "metro-transform-worker": "0.84.4" } }, "sha512-5gIgQHtEpjjvsjKfVtIv23a98LLRV0/y07PDShEwYSytAMlE3FSF8RHXqtHc1sUJL6dn7hnuIBpIbrLXXuVi0A=="],
|
"@expo/metro": ["@expo/metro@56.0.0", "", { "dependencies": { "metro": "0.84.4", "metro-babel-transformer": "0.84.4", "metro-cache": "0.84.4", "metro-cache-key": "0.84.4", "metro-config": "0.84.4", "metro-core": "0.84.4", "metro-file-map": "0.84.4", "metro-minify-terser": "0.84.4", "metro-resolver": "0.84.4", "metro-runtime": "0.84.4", "metro-source-map": "0.84.4", "metro-symbolicate": "0.84.4", "metro-transform-plugins": "0.84.4", "metro-transform-worker": "0.84.4" } }, "sha512-5gIgQHtEpjjvsjKfVtIv23a98LLRV0/y07PDShEwYSytAMlE3FSF8RHXqtHc1sUJL6dn7hnuIBpIbrLXXuVi0A=="],
|
||||||
|
|
||||||
"@expo/metro-config": ["@expo/metro-config@56.0.14", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", "@expo/config": "~56.0.9", "@expo/env": "~2.3.0", "@expo/json-file": "~10.2.0", "@expo/metro": "~56.0.0", "@expo/require-utils": "^56.1.3", "@expo/spawn-async": "^1.8.0", "@jridgewell/gen-mapping": "^0.3.13", "@jridgewell/remapping": "^2.3.5", "@jridgewell/sourcemap-codec": "^1.5.5", "browserslist": "^4.25.0", "chalk": "^4.1.0", "debug": "^4.3.2", "getenv": "^2.0.0", "glob": "^13.0.0", "hermes-parser": "^0.33.3", "jsc-safe-url": "^0.2.4", "lightningcss": "^1.30.1", "picomatch": "^4.0.4", "postcss": "^8.5.14", "resolve-from": "^5.0.0" }, "peerDependencies": { "expo": "*" }, "optionalPeers": ["expo"] }, "sha512-O3CIHruaTJhswPAf/nf3i8QQ3f2jl+mEwSea1eb3khuplabdy/wTQz+JvHN8VGUFyg7JKwUGU1QfO6T3JiSQqA=="],
|
"@expo/metro-config": ["@expo/metro-config@56.0.17", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", "@expo/config": "~56.0.12", "@expo/env": "~2.3.1", "@expo/json-file": "~10.2.0", "@expo/metro": "~56.0.0", "@expo/require-utils": "^56.1.5", "@expo/spawn-async": "^1.8.0", "@jridgewell/gen-mapping": "^0.3.13", "@jridgewell/remapping": "^2.3.5", "@jridgewell/sourcemap-codec": "^1.5.5", "browserslist": "^4.25.0", "chalk": "^4.1.0", "debug": "^4.3.2", "getenv": "^2.0.0", "glob": "^13.0.0", "hermes-parser": "^0.33.3", "jsc-safe-url": "^0.2.4", "lightningcss": "^1.30.1", "picomatch": "^4.0.4", "postcss": "^8.5.14", "resolve-from": "^5.0.0" }, "peerDependencies": { "expo": "*" }, "optionalPeers": ["expo"] }, "sha512-kxCDDCTPbd29gqVhic7fGAPzXbW+fNWerDeXNJdaoO7rutEwogEeud7UHpX4G0fZNMbio1eM5mAQa+edCm/IRw=="],
|
||||||
|
|
||||||
"@expo/metro-file-map": ["@expo/metro-file-map@56.0.3", "", { "dependencies": { "debug": "^4.3.4", "fb-watchman": "^2.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" } }, "sha512-5OGW3z8LgEYgMJOR7F3pC8llFLkb1fVqwAewbCl6S4Vkha8AFQMwOjT+9Wbka+V4rmpljpGqOnMhF4xZbD961w=="],
|
"@expo/metro-file-map": ["@expo/metro-file-map@56.0.3", "", { "dependencies": { "debug": "^4.3.4", "fb-watchman": "^2.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" } }, "sha512-5OGW3z8LgEYgMJOR7F3pC8llFLkb1fVqwAewbCl6S4Vkha8AFQMwOjT+9Wbka+V4rmpljpGqOnMhF4xZbD961w=="],
|
||||||
|
|
||||||
"@expo/metro-runtime": ["@expo/metro-runtime@56.0.15", "", { "dependencies": { "@expo/log-box": "^56.0.13", "anser": "^1.4.9", "pretty-format": "^29.7.0", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-dom": "*", "react-native": "*" }, "optionalPeers": ["react-dom"] }, "sha512-WIWeVsL6kCSB57oYZdUA4MTkH7c67UFMIjdNoQzKXwxZYwBFE/xL2cGPDC3z8RWt0femzJTVxAVZUOW/hiqRzA=="],
|
"@expo/metro-runtime": ["@expo/metro-runtime@56.0.17", "", { "dependencies": { "@expo/log-box": "^56.0.14", "anser": "^1.4.9", "pretty-format": "^29.7.0", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-dom": "*", "react-native": "*" }, "optionalPeers": ["react-dom"] }, "sha512-mYZXMOX3SYqF4VC4G3eVq8+Hd0YfEjs/sSf2eKpqJl54fDtBxeJIu5DLHg9YIMsICPhUZ/r73syEm0835ZEM+g=="],
|
||||||
|
|
||||||
"@expo/osascript": ["@expo/osascript@2.6.0", "", { "dependencies": { "@expo/spawn-async": "^1.8.0" } }, "sha512-QvqDBlJXa8CS2vRORJ4wEflY1m0vVI07uSJdIRgBrLxRPBcsrXxrtU7+wXRXMqfq9zLwNP9XbvRsXF2omoDylg=="],
|
"@expo/osascript": ["@expo/osascript@2.7.1", "", { "dependencies": { "@expo/spawn-async": "^1.8.0" } }, "sha512-Zn03EX6In7ts2lPUW2ESUSkEhEWQN1qqsiXjadtZMJOuZRkMiAg1ZQHuvz9DjByDWNJ2pBwAGyrts9lj9k389g=="],
|
||||||
|
|
||||||
"@expo/package-manager": ["@expo/package-manager@1.12.1", "", { "dependencies": { "@expo/json-file": "^10.2.0", "@expo/spawn-async": "^1.8.0", "chalk": "^4.0.0", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "resolve-workspace-root": "^2.0.0" } }, "sha512-fQLiFAcFRWF53mtuLK32SUJQ1ahhrTcBZPZPedYTiUT5ha5FF+UO6bPtCc0Y/hgj0/m3HCGBAuSHjbg2kI9oPQ=="],
|
"@expo/package-manager": ["@expo/package-manager@1.13.1", "", { "dependencies": { "@expo/json-file": "^11.0.1", "@expo/spawn-async": "^1.8.0", "chalk": "^4.0.0", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "resolve-workspace-root": "^2.0.0" } }, "sha512-y/K+CaYYpZpNGZhSX4HyLT/vyIunFjNfyoxNysPBCefeLKI/VCx6f9LNPzrxayr3rCYO5bl9O8H+HRQK265Nkg=="],
|
||||||
|
|
||||||
"@expo/plist": ["@expo/plist@0.7.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-vrpryU1GoqSIRNqRB2D3IjXDmzNYfiQpEF6AH/xknlD7eiYmEDt3mb26V7cLcedcPG8PY/1xWHdBXVQJfEAh6Q=="],
|
"@expo/plist": ["@expo/plist@0.7.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-vrpryU1GoqSIRNqRB2D3IjXDmzNYfiQpEF6AH/xknlD7eiYmEDt3mb26V7cLcedcPG8PY/1xWHdBXVQJfEAh6Q=="],
|
||||||
|
|
||||||
"@expo/prebuild-config": ["@expo/prebuild-config@56.0.16", "", { "dependencies": { "@expo/config": "~56.0.9", "@expo/config-plugins": "~56.0.9", "@expo/config-types": "^56.0.6", "@expo/image-utils": "^0.10.1", "@expo/json-file": "^10.2.0", "@react-native/normalize-colors": "0.85.3", "debug": "^4.3.1", "expo-modules-autolinking": "~56.0.16", "resolve-from": "^5.0.0", "semver": "^7.6.0" } }, "sha512-ce9ENfPWO4WUWUVQz0OaqL3KYZ7YofP8O35ncnn7CHCaKwQ7BqxcCGJbh+qvP1UjlWeNB3CjHPrXXJ3bnZwlJw=="],
|
"@expo/prebuild-config": ["@expo/prebuild-config@56.0.20", "", { "dependencies": { "@expo/config": "~56.0.12", "@expo/config-plugins": "~56.0.13", "@expo/config-types": "^56.0.7", "@expo/image-utils": "^0.10.3", "@expo/json-file": "^10.2.0", "@react-native/normalize-colors": "0.85.3", "debug": "^4.3.1", "expo-modules-autolinking": "~56.0.20", "resolve-from": "^5.0.0", "semver": "^7.6.0" } }, "sha512-S8K1lg1cqREpnUtWAhDSOitTVJssFVhJAAdtNSi1mZaG/HL5QHUB+dogHIHijfdWKBj4KPGGjBfo6GqdmzzgtA=="],
|
||||||
|
|
||||||
"@expo/react-native-action-sheet": ["@expo/react-native-action-sheet@4.1.1", "", { "dependencies": { "@types/hoist-non-react-statics": "^3.3.1", "hoist-non-react-statics": "^3.3.0" }, "peerDependencies": { "react": ">=18.0.0" } }, "sha512-4KRaba2vhqDRR7ObBj6nrD5uJw8ePoNHdIOMETTpgGTX7StUbrF4j/sfrP1YUyaPEa1P8FXdwG6pB+2WtrJd1A=="],
|
"@expo/react-native-action-sheet": ["@expo/react-native-action-sheet@4.1.1", "", { "dependencies": { "@types/hoist-non-react-statics": "^3.3.1", "hoist-non-react-statics": "^3.3.0" }, "peerDependencies": { "react": ">=18.0.0" } }, "sha512-4KRaba2vhqDRR7ObBj6nrD5uJw8ePoNHdIOMETTpgGTX7StUbrF4j/sfrP1YUyaPEa1P8FXdwG6pB+2WtrJd1A=="],
|
||||||
|
|
||||||
"@expo/require-utils": ["@expo/require-utils@56.1.3", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8" }, "peerDependencies": { "typescript": "^5.0.0 || ^5.0.0-0 || ^6.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KyLeOn/zzQSvuPpV5YhB/FPKnpQytno4luN918bGdPDssLBoS3N/0UbC3W0rJAn9kSFu+XpfR81eABRVsSdfgQ=="],
|
"@expo/require-utils": ["@expo/require-utils@56.1.5", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8" }, "peerDependencies": { "typescript": "^5.0.0 || ^5.0.0-0 || ^6.0.0" }, "optionalPeers": ["typescript"] }, "sha512-3wsdJLSpELhRPEMZCCREqAAFRGG23BUWJHGKIUxyqGkWvy5KPdzlBxGlVVb/6h9gNOflqgTtvTWaP2EuHoQ1HQ=="],
|
||||||
|
|
||||||
"@expo/router-server": ["@expo/router-server@56.0.14", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "@expo/metro-runtime": "^56.0.15", "expo": "*", "expo-constants": "^56.0.18", "expo-font": "^56.0.6", "expo-router": "*", "expo-server": "^56.0.5", "react": "*", "react-dom": "*", "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" }, "optionalPeers": ["@expo/metro-runtime", "expo-router", "react-dom", "react-server-dom-webpack"] }, "sha512-2UCTtZfcq1ZPgp3wk8/+sq9DvFI9UxrPr1jcEKMAF2DGAJLosnpc8GWNNg2hkjt6SHUOdFHIPxujWPYyho2y3A=="],
|
"@expo/router-server": ["@expo/router-server@56.0.16", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "@expo/metro-runtime": "^56.0.16", "expo": "*", "expo-constants": "^56.0.20", "expo-font": "^56.0.7", "expo-router": "*", "expo-server": "^56.0.5", "react": "*", "react-dom": "*", "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" }, "optionalPeers": ["@expo/metro-runtime", "expo-router", "react-dom", "react-server-dom-webpack"] }, "sha512-df5BI5GFnSKqAhuALTPIbuHisd4CUhuYam7pwF9Dt1D6BinPctsteWWDm/sEGXdLVhsub4NPI7tvUHo/HWL7aA=="],
|
||||||
|
|
||||||
"@expo/schema-utils": ["@expo/schema-utils@56.0.1", "", {}, "sha512-CZ/+mYbQmWeOnkCGlWy9K+lFxbJSMFY7+TqBZcKzBSTU5Q7IGRvn/sOG3TdNjIdLPmbA8xe7R/c3UUQ28R9i9w=="],
|
"@expo/schema-utils": ["@expo/schema-utils@56.0.2", "", {}, "sha512-WcOH1E6rwxRqNwBzPOVhd5GBbMlS04+5n+ZMdhdwKOg/Kt3suV+F8F6An+z8mUJ8eSuMM3HD9Sj09t7vc/Xjkw=="],
|
||||||
|
|
||||||
"@expo/sdk-runtime-versions": ["@expo/sdk-runtime-versions@1.0.0", "", {}, "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ=="],
|
"@expo/sdk-runtime-versions": ["@expo/sdk-runtime-versions@1.0.0", "", {}, "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ=="],
|
||||||
|
|
||||||
@@ -411,7 +413,7 @@
|
|||||||
|
|
||||||
"@expo/sudo-prompt": ["@expo/sudo-prompt@9.3.2", "", {}, "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw=="],
|
"@expo/sudo-prompt": ["@expo/sudo-prompt@9.3.2", "", {}, "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw=="],
|
||||||
|
|
||||||
"@expo/ui": ["@expo/ui@56.0.18", "", { "dependencies": { "sf-symbols-typescript": "^2.1.0", "vaul": "^1.1.2" }, "peerDependencies": { "@babel/core": "*", "expo": "*", "react": "*", "react-dom": "*", "react-native": "*", "react-native-reanimated": "*", "react-native-worklets": "*" }, "optionalPeers": ["@babel/core", "react-dom", "react-native-reanimated", "react-native-worklets"] }, "sha512-2XgH5obigGtXm8zlb/V3g87NSiIcBcJ1xoQOEQYPoExL1DCNsHzaIecTh1XG/f/45ardo4OZNJwpbfYJ9X3qrQ=="],
|
"@expo/ui": ["@expo/ui@56.0.22", "", { "dependencies": { "sf-symbols-typescript": "^2.1.0", "vaul": "^1.1.2" }, "peerDependencies": { "@babel/core": "*", "expo": "*", "react": "*", "react-dom": "*", "react-native": "*", "react-native-worklets": "*" }, "optionalPeers": ["@babel/core", "react-dom", "react-native-worklets"] }, "sha512-B6CXc+WJg2GE4KF6LrCPTf+ozMWKMG9TAVc//gQYUsLzFZzm0vCgUqXRiaNJ6BukpHoZPLKsQZ95VsSyTVda3A=="],
|
||||||
|
|
||||||
"@expo/vector-icons": ["@expo/vector-icons@15.1.1", "", { "peerDependencies": { "expo-font": ">=14.0.4", "react": "*", "react-native": "*" } }, "sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw=="],
|
"@expo/vector-icons": ["@expo/vector-icons@15.1.1", "", { "peerDependencies": { "expo-font": ">=14.0.4", "react": "*", "react-native": "*" } }, "sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw=="],
|
||||||
|
|
||||||
@@ -471,7 +473,7 @@
|
|||||||
|
|
||||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
"@nodable/entities": ["@nodable/entities@2.2.0", "", {}, "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg=="],
|
"@nodable/entities": ["@nodable/entities@3.0.0", "", {}, "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw=="],
|
||||||
|
|
||||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||||
|
|
||||||
@@ -479,37 +481,37 @@
|
|||||||
|
|
||||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||||
|
|
||||||
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.4", "", {}, "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ=="],
|
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.5", "", {}, "sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg=="],
|
||||||
|
|
||||||
"@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.10", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-slot": "1.3.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IVVz4EvBcKjrzKgof714qDnz/SzQAkLA2Emh5edlHbgcE6fNd3Un6CJLlaYcnm8N4JmAtzQgse4dOKxcD2yc9g=="],
|
"@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.12", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.2.0", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-slot": "1.3.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q=="],
|
||||||
|
|
||||||
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA=="],
|
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA=="],
|
||||||
|
|
||||||
"@radix-ui/react-context": ["@radix-ui/react-context@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg=="],
|
"@radix-ui/react-context": ["@radix-ui/react-context@1.2.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg=="],
|
||||||
|
|
||||||
"@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.17", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.13", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.10", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-portal": "1.1.12", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-slot": "1.3.0", "@radix-ui/react-use-controllable-state": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TDTYmpdq8dI2+Xgvgj9AJ8Ghqq+Eph/TRVEdaFQPDItIY+6QSkU7MJMeevw1568Yw/2Ijz8BTphPSP2XejKphw=="],
|
"@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.19", "", { "dependencies": { "@radix-ui/primitive": "1.1.5", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.2.0", "@radix-ui/react-dismissable-layer": "1.1.15", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.12", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-portal": "1.1.13", "@radix-ui/react-presence": "1.1.7", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-slot": "1.3.0", "@radix-ui/react-use-controllable-state": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+HhbN2+YtkRgVirjZ2afMeutQRuGOrdkWR5+EFC58SJojGmtyNQwYzgi6tHBpOxvFHefMtPeHdgtjz0BOGxFQg=="],
|
||||||
|
|
||||||
"@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA=="],
|
"@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA=="],
|
||||||
|
|
||||||
"@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-escape-keydown": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-2v+zNAWWe0ySxgC0D0yeXMPQ23xZVgXZTerTz+JKlmdRj6gfTqmCcR29jb6d290DezXPGgruHWDX/vYUebtErg=="],
|
"@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.5", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-effect-event": "0.0.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA=="],
|
||||||
|
|
||||||
"@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q=="],
|
"@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q=="],
|
||||||
|
|
||||||
"@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.10", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-callback-ref": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fas/lXQqhVvqwAb64s5RFeHiHYElZ6SUQbZaNd6EkfhP/Al7wTIQ9WIR4QVX475tlu5yFCEdDcJH6/UwsZjMWw=="],
|
"@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.12", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-use-callback-ref": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A=="],
|
||||||
|
|
||||||
"@radix-ui/react-id": ["@radix-ui/react-id@1.1.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA=="],
|
"@radix-ui/react-id": ["@radix-ui/react-id@1.1.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA=="],
|
||||||
|
|
||||||
"@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.12", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m309havGzsjLHHaIX50G5PlvRs3xkgPCsGk/5PTvYm8D5q33yG0J7w/712PTOhid7NTaFETtnSXjngHQavvhVw=="],
|
"@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.13", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA=="],
|
||||||
|
|
||||||
"@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.6", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ=="],
|
"@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.7", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA=="],
|
||||||
|
|
||||||
"@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.6", "", { "dependencies": { "@radix-ui/react-slot": "1.3.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wetd0QI77DbvrPpTAvH1SqOxsYF2wZe5TNxqwOd5Ty4XDpV3dpV0s8K/1MGMJBeY5o7lg8ub5VIt1Ub+yVen6g=="],
|
"@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.7", "", { "dependencies": { "@radix-ui/react-slot": "1.3.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ=="],
|
||||||
|
|
||||||
"@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-collection": "1.1.10", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9gkwneI0guf8JDmrFxPjJF6Ozzgioyw+/lonYNCwefS9ZHA05er0BVHiXr+LbWGHxUfczvMY6G1oiZZi1VzjRw=="],
|
"@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.5", "@radix-ui/react-collection": "1.1.12", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.2.0", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-is-hydrated": "0.1.1", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg=="],
|
||||||
|
|
||||||
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.3.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA=="],
|
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.3.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA=="],
|
||||||
|
|
||||||
"@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-roving-focus": "1.1.13", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kxc9gI6/HfcU4nfMMVS3AmQK414kbU1IE6UCJmMmxjhO3cRPXOyYnmvyKD+ODt7q56nRq9l7Wovi6uaGwKgMlg=="],
|
"@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.17", "", { "dependencies": { "@radix-ui/primitive": "1.1.5", "@radix-ui/react-context": "1.2.0", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-presence": "1.1.7", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-roving-focus": "1.1.15", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-nRyXnrAVCwjeXcHbvEbLS6ndbTeKHG1RqCP4A8Gw5L4cemDzPXdD8rAmr6wet0v57R69wGvuIIsFjHSVkZiMzQ=="],
|
||||||
|
|
||||||
"@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw=="],
|
"@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw=="],
|
||||||
|
|
||||||
@@ -517,7 +519,7 @@
|
|||||||
|
|
||||||
"@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.3", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA=="],
|
"@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.3", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA=="],
|
||||||
|
|
||||||
"@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.2", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2uVLvLjgO7NZCWw01/FdqRwmA42J0BcjPMUCA+koFEOAb+zjqIP7SiFz/7zWPrKnVmSqr76Omq2ALyCuX4dhLw=="],
|
"@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A=="],
|
||||||
|
|
||||||
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA=="],
|
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA=="],
|
||||||
|
|
||||||
@@ -579,15 +581,15 @@
|
|||||||
|
|
||||||
"@react-native/normalize-colors": ["@react-native/normalize-colors@0.85.3", "", {}, "sha512-hj0PScZEhIbcOvQV5yMKX3ha4XEIOy/SVE1Rrpp0beW0dpNLOgSC7KDxGewmDnIHK9YdQUXGY9eMEfShUMIaZw=="],
|
"@react-native/normalize-colors": ["@react-native/normalize-colors@0.85.3", "", {}, "sha512-hj0PScZEhIbcOvQV5yMKX3ha4XEIOy/SVE1Rrpp0beW0dpNLOgSC7KDxGewmDnIHK9YdQUXGY9eMEfShUMIaZw=="],
|
||||||
|
|
||||||
"@react-navigation/core": ["@react-navigation/core@7.21.1", "", { "dependencies": { "@react-navigation/routers": "^7.6.0", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-9eOK71VFEyJjm1bP8o4Gf7YYppWPZ+RdNIjTc+WbSM7AFEH0XXEIHkkhpIBuB416sDlRZ1CRxHk2frh1HPBZqw=="],
|
"@react-navigation/core": ["@react-navigation/core@7.21.8", "", { "dependencies": { "@react-navigation/routers": "^7.6.2", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-jsCLOIgB3eYv7KQsdSpyW2mC0wfInWYAiFVkPn3ZUQJ1FgjwbqY2//JaCWgTRGKBj3uDy36z5SeYx6WIYpweEw=="],
|
||||||
|
|
||||||
"@react-navigation/elements": ["@react-navigation/elements@2.9.25", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.3.3", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-cV7Hny55aE/uge6f4UB0pbGQbFl3XjXLMW+lTBNuJs8P7a9tuf7dkkPHxxgnLIvxE+KJVI2K8CGabfDk4Ht0Sg=="],
|
"@react-navigation/elements": ["@react-navigation/elements@2.9.33", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.3.11", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-tQvZo1pakH6O9wkh5fWD8KlsTCG4eYxp2HD5Cjr9xjpTjqsnS13/BQV21Cu6fcKVCQ89SY6w+0+uXeKc3CzAVg=="],
|
||||||
|
|
||||||
"@react-navigation/material-top-tabs": ["@react-navigation/material-top-tabs@7.4.28", "", { "dependencies": { "@react-navigation/elements": "^2.9.19", "color": "^4.2.3", "react-native-tab-view": "^4.3.0" }, "peerDependencies": { "@react-navigation/native": "^7.2.5", "react": ">= 18.2.0", "react-native": "*", "react-native-pager-view": ">= 6.0.0", "react-native-safe-area-context": ">= 4.0.0" } }, "sha512-WZHJSGV2PQOD2Vr9LF8apGvcsbDKukzF3Fhh8xVNIesqaSi9TPProv4dRw6YkenUkjvFVZYkOjvwAJOToePVpA=="],
|
"@react-navigation/material-top-tabs": ["@react-navigation/material-top-tabs@7.4.28", "", { "dependencies": { "@react-navigation/elements": "^2.9.19", "color": "^4.2.3", "react-native-tab-view": "^4.3.0" }, "peerDependencies": { "@react-navigation/native": "^7.2.5", "react": ">= 18.2.0", "react-native": "*", "react-native-pager-view": ">= 6.0.0", "react-native-safe-area-context": ">= 4.0.0" } }, "sha512-WZHJSGV2PQOD2Vr9LF8apGvcsbDKukzF3Fhh8xVNIesqaSi9TPProv4dRw6YkenUkjvFVZYkOjvwAJOToePVpA=="],
|
||||||
|
|
||||||
"@react-navigation/native": ["@react-navigation/native@7.3.3", "", { "dependencies": { "@react-navigation/core": "^7.21.1", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "standard-navigation": "^0.0.7", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-VjZngfeiyJestZPT99CKHRi3qOR6XwnEEPWb6uHF/L7fBI6RBVP842iJf61MUHRTzsWPUT+epJqdf1wm8N5YkQ=="],
|
"@react-navigation/native": ["@react-navigation/native@7.3.11", "", { "dependencies": { "@react-navigation/core": "^7.21.8", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "standard-navigation": "^0.0.8", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-CWg3lLOVYwnbjPdIzJWrpqSF6F2+1F6QMUvQfLFA6MizwOTUrkZ1w1q7a7m0Pis5ful+EFpoPqHYuLoQ5XmoCw=="],
|
||||||
|
|
||||||
"@react-navigation/routers": ["@react-navigation/routers@7.6.0", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-lblhDXfS75jLc7G2K7BZGM+7cjqQXk13X/MA4fq/12r62zM+fBhhreLzYflSitrDDXFRJpSvJXy0ziiGU04Xow=="],
|
"@react-navigation/routers": ["@react-navigation/routers@7.6.2", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-cYzWE/+kcX6RAaX1/peL07f4aIpxeE7uNDr89qJpfGuVz3WHs9dGE1QwbCyCK95fVYn1tvsOmbXbvR0o43uqKQ=="],
|
||||||
|
|
||||||
"@shopify/flash-list": ["@shopify/flash-list@2.0.3", "", { "dependencies": { "tslib": "2.8.1" }, "peerDependencies": { "@babel/runtime": "*", "react": "*", "react-native": "*" } }, "sha512-jUlHuZFoPdqRCDvOqsb2YkTttRPyV8Tb/EjCx3gE2wjr4UTM+fE0Ltv9bwBg0K7yo/SxRNXaW7xu5utusRb0xA=="],
|
"@shopify/flash-list": ["@shopify/flash-list@2.0.3", "", { "dependencies": { "tslib": "2.8.1" }, "peerDependencies": { "@babel/runtime": "*", "react": "*", "react-native": "*" } }, "sha512-jUlHuZFoPdqRCDvOqsb2YkTttRPyV8Tb/EjCx3gE2wjr4UTM+fE0Ltv9bwBg0K7yo/SxRNXaW7xu5utusRb0xA=="],
|
||||||
|
|
||||||
@@ -597,23 +599,23 @@
|
|||||||
|
|
||||||
"@sideway/pinpoint": ["@sideway/pinpoint@2.0.0", "", {}, "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="],
|
"@sideway/pinpoint": ["@sideway/pinpoint@2.0.0", "", {}, "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="],
|
||||||
|
|
||||||
"@sinclair/typebox": ["@sinclair/typebox@0.27.10", "", {}, "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA=="],
|
"@sinclair/typebox": ["@sinclair/typebox@0.27.12", "", {}, "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g=="],
|
||||||
|
|
||||||
"@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.4.3", "", { "bin": { "intent": "bin/intent.js" } }, "sha512-OZI6QyULw0FI0wjgmeYzCIfbgPsOEzwJtCpa69XrfLMtNXLGnz3d/dIabk7frg0TmHo+Ah49w5I4KC7Tufwsvw=="],
|
"@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.4.4", "", { "bin": { "intent": "./bin/intent.js" } }, "sha512-6T5Yop/793YI+H+5J8Hsyj4kCih9sl4t3ElLgKioW5hk3ocn+ZdSJ94tT7vL7uabxSugWYBZlOTMPzEw2puvQw=="],
|
||||||
|
|
||||||
"@tanstack/pacer": ["@tanstack/pacer@0.18.0", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.0", "@tanstack/store": "^0.8.0" } }, "sha512-qhCRSFei0hokQr3xYcQXqxsRD/LKlgHCxHXtKHrQoImp4x2Zu6tUOpUGVH4y2qexIrzSu3aibQBNNfC3Eay6Mg=="],
|
"@tanstack/pacer": ["@tanstack/pacer@0.18.0", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.0", "@tanstack/store": "^0.8.0" } }, "sha512-qhCRSFei0hokQr3xYcQXqxsRD/LKlgHCxHXtKHrQoImp4x2Zu6tUOpUGVH4y2qexIrzSu3aibQBNNfC3Eay6Mg=="],
|
||||||
|
|
||||||
"@tanstack/query-core": ["@tanstack/query-core@5.101.0", "", {}, "sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow=="],
|
"@tanstack/query-core": ["@tanstack/query-core@5.101.2", "", {}, "sha512-hH5MLoJhF7KaIGd7q3xTXGXvslI+GYlM1Z/35aSHHWaCJWB7XvTSHYuV3eM7tw+aE0mT/xMro4M4Q9rCGHT0lw=="],
|
||||||
|
|
||||||
"@tanstack/query-persist-client-core": ["@tanstack/query-persist-client-core@5.101.0", "", { "dependencies": { "@tanstack/query-core": "5.101.0" } }, "sha512-LH99WepGVLwlLfuOcQcPK7f3Xg/Gf+xlMMIj9xWu/8oQ3egnDzjr+a4HvEmi6PGob5SmGXvmDKZaH5+In9dzjw=="],
|
"@tanstack/query-persist-client-core": ["@tanstack/query-persist-client-core@5.101.2", "", { "dependencies": { "@tanstack/query-core": "5.101.2" } }, "sha512-rgMsbvBVpSPDUsprC9FYxojdG0Q1LH6yq1i3DXz2aps4VEqv2l4Msj3YDqIifU3xkl/DrYe9YWntZAJLrM6xTg=="],
|
||||||
|
|
||||||
"@tanstack/query-sync-storage-persister": ["@tanstack/query-sync-storage-persister@5.101.0", "", { "dependencies": { "@tanstack/query-core": "5.101.0", "@tanstack/query-persist-client-core": "5.101.0" } }, "sha512-UAGbsIJe9vkV/rbFxUBOPH27Eu6K17KuVLfK1mSy8qoSUJ4qt6JKGMxA8NMUoowbPG0ZnTfRt1Y5SFsqfqlfcA=="],
|
"@tanstack/query-sync-storage-persister": ["@tanstack/query-sync-storage-persister@5.101.2", "", { "dependencies": { "@tanstack/query-core": "5.101.2", "@tanstack/query-persist-client-core": "5.101.2" } }, "sha512-hRyLEJvkUWtfCUA3Lf5vD1CrXbHHK4pZNM/iNd7JM5GgC4CWzGIHkVkeg8zC2LV11KceKObKgjNuMSg3b6NOjA=="],
|
||||||
|
|
||||||
"@tanstack/react-pacer": ["@tanstack/react-pacer@0.19.4", "", { "dependencies": { "@tanstack/pacer": "0.18.0", "@tanstack/react-store": "^0.8.0" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-coj8ULAuR0qFpjAKD44gTgRuZyjxU6Xu+IX5MwwYvr4e61OtZcJshaExoOBKpCGde0Edb12jDnzzj2Im13Qm9Q=="],
|
"@tanstack/react-pacer": ["@tanstack/react-pacer@0.19.4", "", { "dependencies": { "@tanstack/pacer": "0.18.0", "@tanstack/react-store": "^0.8.0" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-coj8ULAuR0qFpjAKD44gTgRuZyjxU6Xu+IX5MwwYvr4e61OtZcJshaExoOBKpCGde0Edb12jDnzzj2Im13Qm9Q=="],
|
||||||
|
|
||||||
"@tanstack/react-query": ["@tanstack/react-query@5.100.14", "", { "dependencies": { "@tanstack/query-core": "5.100.14" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-oOr6aRdSFEwWhzxEkD/9ZcItM3+LjBSkeVmadWKwUssAHTsqd/7bOjWrX4AbvEkoEhgAxzN0Xk6H/aYzXiYBAw=="],
|
"@tanstack/react-query": ["@tanstack/react-query@5.100.14", "", { "dependencies": { "@tanstack/query-core": "5.100.14" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-oOr6aRdSFEwWhzxEkD/9ZcItM3+LjBSkeVmadWKwUssAHTsqd/7bOjWrX4AbvEkoEhgAxzN0Xk6H/aYzXiYBAw=="],
|
||||||
|
|
||||||
"@tanstack/react-query-persist-client": ["@tanstack/react-query-persist-client@5.101.0", "", { "dependencies": { "@tanstack/query-persist-client-core": "5.101.0" }, "peerDependencies": { "@tanstack/react-query": "^5.101.0", "react": "^18 || ^19" } }, "sha512-AUcdBgz8V6sM9axzdqkVmWjYSOETkhr6yAZSBnEFyZT2jo6vkFq3UrpRuxGs6fmhKMWv8FA+ZJGcbaKPaoAElQ=="],
|
"@tanstack/react-query-persist-client": ["@tanstack/react-query-persist-client@5.101.2", "", { "dependencies": { "@tanstack/query-persist-client-core": "5.101.2" }, "peerDependencies": { "@tanstack/react-query": "^5.101.2", "react": "^18 || ^19" } }, "sha512-19UpaRtf0lvB2QAJ2MoixxtGf3osMOd508g99EsttUj4+3eLs+ulnNgYjB7AkQMHrRlcbVXqpVNqWkB4a7g8Zw=="],
|
||||||
|
|
||||||
"@tanstack/react-store": ["@tanstack/react-store@0.8.1", "", { "dependencies": { "@tanstack/store": "0.8.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-XItJt+rG8c5Wn/2L/bnxys85rBpm0BfMbhb4zmPVLXAKY9POrp1xd6IbU4PKoOI+jSEGc3vntPRfLGSgXfE2Ig=="],
|
"@tanstack/react-store": ["@tanstack/react-store@0.8.1", "", { "dependencies": { "@tanstack/store": "0.8.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-XItJt+rG8c5Wn/2L/bnxys85rBpm0BfMbhb4zmPVLXAKY9POrp1xd6IbU4PKoOI+jSEGc3vntPRfLGSgXfE2Ig=="],
|
||||||
|
|
||||||
@@ -657,7 +659,7 @@
|
|||||||
|
|
||||||
"@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="],
|
"@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="],
|
||||||
|
|
||||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.1", "", {}, "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ=="],
|
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.3", "", {}, "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg=="],
|
||||||
|
|
||||||
"@vibrant/color": ["@vibrant/color@4.0.4", "", {}, "sha512-Fq2tAszz4QOPWfHZ+KuEAchXUD8i594BM2fOJt8dI/fvYbiVoBycBF/BlNH6F4IWBubxXoPqD4JmmAHvFYbNew=="],
|
"@vibrant/color": ["@vibrant/color@4.0.4", "", {}, "sha512-Fq2tAszz4QOPWfHZ+KuEAchXUD8i594BM2fOJt8dI/fvYbiVoBycBF/BlNH6F4IWBubxXoPqD4JmmAHvFYbNew=="],
|
||||||
|
|
||||||
@@ -695,6 +697,8 @@
|
|||||||
|
|
||||||
"agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
|
"agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
|
||||||
|
|
||||||
|
"agent-cli-detector": ["agent-cli-detector@0.1.4", "", { "bin": { "agent-cli-detector": "dist/cli.js" } }, "sha512-qPgevFvpaQoBaRJVKzr8R7h1WPvV3DtbgRIQlne4le66KBzXx5hNBwo/+NTw67LgkKBlhCzksrdautpUdlls0Q=="],
|
||||||
|
|
||||||
"anser": ["anser@1.4.10", "", {}, "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww=="],
|
"anser": ["anser@1.4.10", "", {}, "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww=="],
|
||||||
|
|
||||||
"ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="],
|
"ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="],
|
||||||
@@ -711,7 +715,7 @@
|
|||||||
|
|
||||||
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
||||||
|
|
||||||
"anynum": ["anynum@1.0.0", "", {}, "sha512-xjR9/zBVnUOP6ztMIIgShjsxui80nQUQH+5xJnvrYLs+90bF25/KJqaAi8mk+B4RDtX1Nspi6fmp4YTEts8SfA=="],
|
"anynum": ["anynum@1.0.1", "", {}, "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A=="],
|
||||||
|
|
||||||
"appdirsjs": ["appdirsjs@1.2.7", "", {}, "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw=="],
|
"appdirsjs": ["appdirsjs@1.2.7", "", {}, "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw=="],
|
||||||
|
|
||||||
@@ -731,7 +735,7 @@
|
|||||||
|
|
||||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||||
|
|
||||||
"axios": ["axios@1.18.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw=="],
|
"axios": ["axios@1.18.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g=="],
|
||||||
|
|
||||||
"babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.17", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w=="],
|
"babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.17", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w=="],
|
||||||
|
|
||||||
@@ -747,17 +751,17 @@
|
|||||||
|
|
||||||
"babel-plugin-transform-flow-enums": ["babel-plugin-transform-flow-enums@0.0.2", "", { "dependencies": { "@babel/plugin-syntax-flow": "^7.12.1" } }, "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ=="],
|
"babel-plugin-transform-flow-enums": ["babel-plugin-transform-flow-enums@0.0.2", "", { "dependencies": { "@babel/plugin-syntax-flow": "^7.12.1" } }, "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ=="],
|
||||||
|
|
||||||
"babel-preset-expo": ["babel-preset-expo@56.0.15", "", { "dependencies": { "@babel/generator": "^7.20.5", "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-class-static-block": "^7.27.1", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.28.6", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-plugin-codegen": "0.85.3", "babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.33.3", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4" }, "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", "expo-widgets": "^56.0.18", "react-refresh": ">=0.14.0 <1.0.0" }, "optionalPeers": ["@babel/runtime", "expo", "expo-widgets"] }, "sha512-0MqbQoM6nBUbKvgu2xJ4VixZnUTGTq3HB2WwvOikdO4CiPxbQ+wGA25fOoHHSni5iEFW39wy6y1ookTWlq3wVw=="],
|
"babel-preset-expo": ["babel-preset-expo@56.0.17", "", { "dependencies": { "@babel/generator": "^7.20.5", "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-class-static-block": "^7.27.1", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.28.6", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-plugin-codegen": "0.85.3", "babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.33.3", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4" }, "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", "expo-widgets": "^56.0.22", "react-refresh": ">=0.14.0 <1.0.0" }, "optionalPeers": ["@babel/runtime", "expo", "expo-widgets"] }, "sha512-yanAlbzaMMNqnju/uMnZsf3ltgssG71UubkfD+2iMaoXGNIj+TODEs9hF12jrTX2lzq/zH0HWSuRLZLNalf8fw=="],
|
||||||
|
|
||||||
"badgin": ["badgin@1.2.3", "", {}, "sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw=="],
|
"badgin": ["badgin@1.2.3", "", {}, "sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw=="],
|
||||||
|
|
||||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||||
|
|
||||||
"barcode-detector": ["barcode-detector@3.2.0", "", { "dependencies": { "zxing-wasm": "3.1.0" } }, "sha512-MrT5TT058ptG5YB157pHLfXKVpp0BKEfQBOb8QvzTbatzmLDu85JJ0Gd/sCYwbwdwStJvxsYflrSN6D6E4Ndyw=="],
|
"barcode-detector": ["barcode-detector@3.2.1", "", { "dependencies": { "zxing-wasm": "3.1.1" } }, "sha512-zLL7AbT9uNJBUzYpKg9v5tUA4yQGReExSi60q0g660Mj0wjUhKmN0GrUF3qELo8ITW9THzyJXdZQkXLMnsieiw=="],
|
||||||
|
|
||||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||||
|
|
||||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.37", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig=="],
|
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.43", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ=="],
|
||||||
|
|
||||||
"big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="],
|
"big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="],
|
||||||
|
|
||||||
@@ -775,11 +779,11 @@
|
|||||||
|
|
||||||
"bplist-parser": ["bplist-parser@0.3.2", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ=="],
|
"bplist-parser": ["bplist-parser@0.3.2", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ=="],
|
||||||
|
|
||||||
"brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
|
"brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="],
|
||||||
|
|
||||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||||
|
|
||||||
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
"browserslist": ["browserslist@4.28.6", "", { "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001803", "electron-to-chromium": "^1.5.389", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw=="],
|
||||||
|
|
||||||
"bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="],
|
"bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="],
|
||||||
|
|
||||||
@@ -803,7 +807,7 @@
|
|||||||
|
|
||||||
"camelize": ["camelize@1.0.1", "", {}, "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ=="],
|
"camelize": ["camelize@1.0.1", "", {}, "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ=="],
|
||||||
|
|
||||||
"caniuse-lite": ["caniuse-lite@1.0.30001799", "", {}, "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw=="],
|
"caniuse-lite": ["caniuse-lite@1.0.30001806", "", {}, "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw=="],
|
||||||
|
|
||||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||||
|
|
||||||
@@ -933,7 +937,7 @@
|
|||||||
|
|
||||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||||
|
|
||||||
"electron-to-chromium": ["electron-to-chromium@1.5.374", "", {}, "sha512-HCF5i7izveksHSGqa7mhDh6tr3Uz9Dar2RAjwuh69bw3QGPVObjQIgLwQWeO/Rxp9/r0KdboKy9RbpQDl97fjg=="],
|
"electron-to-chromium": ["electron-to-chromium@1.5.393", "", {}, "sha512-kiDJdIUawuEIcp9XoICKp1iTYDEbgguIPq526N1Q7jIQDeQ3CqoMx71025PI/7E48Ddtw2HuWsVjY7afEgNxmg=="],
|
||||||
|
|
||||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
||||||
@@ -983,33 +987,35 @@
|
|||||||
|
|
||||||
"expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="],
|
"expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="],
|
||||||
|
|
||||||
"expo": ["expo@56.0.12", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "^56.1.16", "@expo/config": "~56.0.9", "@expo/config-plugins": "~56.0.9", "@expo/devtools": "~56.0.2", "@expo/dom-webview": "~56.0.5", "@expo/fingerprint": "^0.19.4", "@expo/local-build-cache-provider": "^56.0.8", "@expo/log-box": "^56.0.13", "@expo/metro": "~56.0.0", "@expo/metro-config": "~56.0.14", "@ungap/structured-clone": "^1.3.0", "babel-preset-expo": "~56.0.15", "expo-asset": "~56.0.17", "expo-constants": "~56.0.18", "expo-file-system": "~56.0.8", "expo-font": "~56.0.7", "expo-keep-awake": "~56.0.3", "expo-modules-autolinking": "~56.0.16", "expo-modules-core": "~56.0.17", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-minimum": "^0.1.2" }, "peerDependencies": { "@expo/metro-runtime": "*", "react": "*", "react-dom": "*", "react-native": "*", "react-native-web": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/metro-runtime", "react-dom", "react-native-web", "react-native-webview"], "bin": { "expo": "bin/cli", "fingerprint": "bin/fingerprint", "expo-modules-autolinking": "bin/autolinking" } }, "sha512-FxgdI/Yqva6iJOThZIHfvxlKPxs4EC4uScUnEswwSArR/Fj9k430O13R590LcOQTsdNsjIs+GBHwjfoAY6vmAQ=="],
|
"expo": ["expo@56.0.16", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "^56.1.20", "@expo/config": "~56.0.12", "@expo/config-plugins": "~56.0.13", "@expo/devtools": "~56.0.2", "@expo/dom-webview": "~56.0.6", "@expo/fingerprint": "^0.19.8", "@expo/local-build-cache-provider": "^56.0.10", "@expo/log-box": "^56.0.14", "@expo/metro": "~56.0.0", "@expo/metro-config": "~56.0.17", "@ungap/structured-clone": "^1.3.0", "babel-preset-expo": "~56.0.17", "expo-asset": "~56.0.20", "expo-constants": "~56.0.21", "expo-file-system": "~56.0.8", "expo-font": "~56.0.7", "expo-keep-awake": "~56.0.3", "expo-modules-autolinking": "~56.0.20", "expo-modules-core": "~56.0.21", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-minimum": "^0.1.2" }, "peerDependencies": { "@expo/metro-runtime": "*", "react": "*", "react-dom": "*", "react-native": "*", "react-native-web": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/metro-runtime", "react-dom", "react-native-web", "react-native-webview"], "bin": { "expo": "bin/cli", "fingerprint": "bin/fingerprint", "expo-modules-autolinking": "bin/autolinking" } }, "sha512-6G1yfBKG3J16HBgF2ehdd1iZBlRmbDF/lB7SG30v4jojgvN03HZkENHujKfuGuBqDBlzkkXjZvpxRVvIUQTAMQ=="],
|
||||||
|
|
||||||
"expo-application": ["expo-application@56.0.3", "", { "peerDependencies": { "expo": "*" } }, "sha512-DdGGPlMuM6cSTeKhbvh6OeLr2O/+EI5BHKYrD+Do8sJPYgLwzGrgESELfyjJCpEhFzT+TgKIdmLmWXhNUQnHiw=="],
|
"expo-application": ["expo-application@56.0.3", "", { "peerDependencies": { "expo": "*" } }, "sha512-DdGGPlMuM6cSTeKhbvh6OeLr2O/+EI5BHKYrD+Do8sJPYgLwzGrgESELfyjJCpEhFzT+TgKIdmLmWXhNUQnHiw=="],
|
||||||
|
|
||||||
"expo-asset": ["expo-asset@56.0.17", "", { "dependencies": { "@expo/image-utils": "^0.10.1", "expo-constants": "~56.0.18" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-GFN5j+8SPkyv0nfsiFHewmdB/D0tL237TsBE/gSfFOFy/J3a52py7IulcSqkA3sQE/u/UlD5BmvP5ssS4//nUg=="],
|
"expo-asset": ["expo-asset@56.0.20", "", { "dependencies": { "@expo/image-utils": "^0.10.3", "expo-constants": "~56.0.21" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-pjVw3JDiaq6Ee15UbDeZpiGNAHJqfOZwY61VPlFiznuuj7f/b2tvegujeIEu2+p6C2/tioZGGD6zWQuJ9Ob3uQ=="],
|
||||||
|
|
||||||
"expo-audio": ["expo-audio@56.0.12", "", { "peerDependencies": { "expo": "*", "expo-asset": "*", "react": "*", "react-native": "*" } }, "sha512-ne2UIO/HsQoBL9e+tGs5N9Sf3NyW5sJMm4sDkexbSJRc2IchLDG+9Msu/+l5N4RlZ8SiF42wRyWsh/Usg+SwOw=="],
|
"expo-audio": ["expo-audio@56.0.12", "", { "peerDependencies": { "expo": "*", "expo-asset": "*", "react": "*", "react-native": "*" } }, "sha512-ne2UIO/HsQoBL9e+tGs5N9Sf3NyW5sJMm4sDkexbSJRc2IchLDG+9Msu/+l5N4RlZ8SiF42wRyWsh/Usg+SwOw=="],
|
||||||
|
|
||||||
"expo-background-task": ["expo-background-task@56.0.19", "", { "dependencies": { "expo-task-manager": "~56.0.19" }, "peerDependencies": { "expo": "*" } }, "sha512-TuFqorlz4HXtWAwQGcg+/URXpBwjwNsAxdis/6m5mhtgBIVbX2gYYv/s6eIf8lh2cAI1zFkb+/bHHuC2aOAGGA=="],
|
"expo-background-task": ["expo-background-task@56.0.22", "", { "dependencies": { "expo-task-manager": "~56.0.22" }, "peerDependencies": { "expo": "*" } }, "sha512-kXl9sn5ZJS4zeRKvpGbxBBTVCezUQFZZdUTbXcnIb+4jUl3jRwuygelyExqQi0f84M5dTLoHnaTxgz9d5y+NXg=="],
|
||||||
|
|
||||||
"expo-blur": ["expo-blur@56.0.3", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-KDDtrpWc2tYlm1WCPaOgBtv+YEGqe5ELheFPIgSNgHt28NQUDcfBcFsA9Us2StDh6osmSD6NbKxOt5bU6PcDbQ=="],
|
"expo-blur": ["expo-blur@56.0.4", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-/rNpe2NDTmMbSktVJb5z7HoX6apYtq8KVKJNjWOQV7NG01+LvctWzFeMNxWSZVWfIKTndqdqABwqcpYmiCRxxg=="],
|
||||||
|
|
||||||
"expo-brightness": ["expo-brightness@56.0.5", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-AkCGW+Lj8I4o2+Yjs1bzjIJz44cgNXfAN+pf01uDwmA1/1JTIy8x1eISvmz6d2r/1OhdyBZxeDkACNLVMDx5zA=="],
|
"expo-brightness": ["expo-brightness@56.0.5", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-AkCGW+Lj8I4o2+Yjs1bzjIJz44cgNXfAN+pf01uDwmA1/1JTIy8x1eISvmz6d2r/1OhdyBZxeDkACNLVMDx5zA=="],
|
||||||
|
|
||||||
"expo-build-properties": ["expo-build-properties@56.0.19", "", { "dependencies": { "@expo/schema-utils": "^56.0.0", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-InoviXcxWosNp4cC7L3SWoiY99Xr2HdgN+LYHb6mUm/BBVxy1mIMrZR+3PJ2gwDZzW6EJNDz8ioASWGHBTmzpA=="],
|
"expo-build-properties": ["expo-build-properties@56.0.23", "", { "dependencies": { "@expo/schema-utils": "^56.0.2", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-xb9QX+x/AsDw8wVdox3jNpf8eVUc8PYq3lycF2OOlmHkmn+ecmxmtdCpB3ZDLjtdem8XrgycgLIba7WTdc7o7w=="],
|
||||||
|
|
||||||
"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-constants": ["expo-constants@56.0.18", "", { "dependencies": { "@expo/env": "~2.3.0" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-8AMtbDGl/WVPnWlmbpGmvcdnNCy9E4PFnwdVwj600vljkMDPSxcAcjw8GVXEPk3PpZ+ngTqsrkltWyj0UKYAxw=="],
|
"expo-clipboard": ["expo-clipboard@56.0.4", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-qb4DYlkiowHYHaUYVT2FN9nk/nI1xShXOUYsI7J9dVpQCOHcGFjCBPX1VAvEW4Ye4/Aagd6IuhOVAq/+scBOiA=="],
|
||||||
|
|
||||||
|
"expo-constants": ["expo-constants@56.0.21", "", { "dependencies": { "@expo/env": "~2.3.1" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-q9LkK9tNTW2z/5hXgpwWoCXO1fBrqn4FgwTiLerM9WOu1wtg99NevAtG3BEG8t5Awtub+NsLidiFZzBat45RKw=="],
|
||||||
|
|
||||||
"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=="],
|
||||||
|
|
||||||
"expo-dev-client": ["expo-dev-client@56.0.20", "", { "dependencies": { "expo-dev-launcher": "~56.0.20", "expo-dev-menu": "~56.0.17", "expo-dev-menu-interface": "~56.0.0", "expo-manifests": "~56.0.4", "expo-updates-interface": "~56.0.1" }, "peerDependencies": { "expo": "*" } }, "sha512-KebW4r8HhIiRrPzs6ZqVhp/so8buyglAO1h4No0Ibr5C2XRnlIoGWCN4zC6rW7IsI3iKUXcofLAQV9OjoxjiwQ=="],
|
"expo-dev-client": ["expo-dev-client@56.0.23", "", { "dependencies": { "expo-dev-launcher": "~56.0.24", "expo-dev-menu": "~56.0.20", "expo-dev-menu-interface": "~56.0.0", "expo-manifests": "~56.0.4", "expo-updates-interface": "~56.0.1" }, "peerDependencies": { "expo": "*" } }, "sha512-3yQc4McvFTxqFpQe0wZ/Ek9bLXIy/ilnHBPy1UqUjzFfarJNd5FUoWp+1ovE8Gt59OPiIsKl+VzPq330hwQxkg=="],
|
||||||
|
|
||||||
"expo-dev-launcher": ["expo-dev-launcher@56.0.20", "", { "dependencies": { "@expo/schema-utils": "^56.0.0", "expo-dev-menu": "~56.0.17", "expo-manifests": "~56.0.4" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-cTuC3GkPl9CTwO3CKnVmEm9qoQ0WairhwvTh6qMlg+zr/QU/tdiU++uDBX67hf9+FuxQOkWGp5khFNosT+0cIg=="],
|
"expo-dev-launcher": ["expo-dev-launcher@56.0.24", "", { "dependencies": { "@expo/schema-utils": "^56.0.2", "expo-dev-menu": "~56.0.20", "expo-manifests": "~56.0.4" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-uzYYpWY2rdLUS+ct9Loku4LGdMp7XwBHPAhuHu/R0SMHl4IxFx8VLFAbOaO34NrDWqB6fqdze1qHoA6DxeuZIA=="],
|
||||||
|
|
||||||
"expo-dev-menu": ["expo-dev-menu@56.0.17", "", { "dependencies": { "expo-dev-menu-interface": "~56.0.0" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-OofRkOOZnaDriSav3JDN4NP2lsLt2eOa/Ryptr5nMD62SwnFyK4R6n6PkPVaDU3LSsZqndAJHmN6inS+oziayQ=="],
|
"expo-dev-menu": ["expo-dev-menu@56.0.20", "", { "dependencies": { "expo-dev-menu-interface": "~56.0.0" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-PJH+RtOWDV7E5oic7cLKN7+AO/oE3QPnHzooxwzGrcjiySCQOvkFC67SNfBCxXXaMFMRf2MwihqY4PllAVAUyw=="],
|
||||||
|
|
||||||
"expo-dev-menu-interface": ["expo-dev-menu-interface@56.0.1", "", { "peerDependencies": { "expo": "*" } }, "sha512-odATx0ZL/Kis10sKSBiKiGQxAB6coSi/KQtKcMhnQVNno6FkRh5/4e5BqcEvpq2rNMTiQp4ytNAQHtdwbPXvGA=="],
|
"expo-dev-menu-interface": ["expo-dev-menu-interface@56.0.1", "", { "peerDependencies": { "expo": "*" } }, "sha512-odATx0ZL/Kis10sKSBiKiGQxAB6coSi/KQtKcMhnQVNno6FkRh5/4e5BqcEvpq2rNMTiQp4ytNAQHtdwbPXvGA=="],
|
||||||
|
|
||||||
@@ -1031,25 +1037,27 @@
|
|||||||
|
|
||||||
"expo-keep-awake": ["expo-keep-awake@56.0.3", "", { "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-CLMJXtEiMKknD3Rpm8CRwE6ZJUzu2yCEmRk1sgfHAJ1zIbuEWY3dpPDubtsnuzWm+2k6Sru+yaFbYsvPWmTiBA=="],
|
"expo-keep-awake": ["expo-keep-awake@56.0.3", "", { "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-CLMJXtEiMKknD3Rpm8CRwE6ZJUzu2yCEmRk1sgfHAJ1zIbuEWY3dpPDubtsnuzWm+2k6Sru+yaFbYsvPWmTiBA=="],
|
||||||
|
|
||||||
|
"expo-key-event": ["expo-key-event@1.9.0", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-ZV/+iAz/0WeACFG4IR+KOTtm/7PTIsl+GaD/PYBM89PhvueQ6zYuvjPb5A47sWX2WjDunzVN0R1rOd/HIjA8aA=="],
|
||||||
|
|
||||||
"expo-linear-gradient": ["expo-linear-gradient@56.0.4", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-KUp1dNSRtuMyiExhf6FJf5YUtmw2cRaPytl10HQi7isj5Yac38udmD55T2tglNYTZlvgT5+oflpyFoH15hmOcw=="],
|
"expo-linear-gradient": ["expo-linear-gradient@56.0.4", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-KUp1dNSRtuMyiExhf6FJf5YUtmw2cRaPytl10HQi7isj5Yac38udmD55T2tglNYTZlvgT5+oflpyFoH15hmOcw=="],
|
||||||
|
|
||||||
"expo-linking": ["expo-linking@56.0.14", "", { "dependencies": { "expo-constants": "~56.0.18", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-IvVQHWC+Cj4fK5qD3iEVYqpU2a4rLW0IpAAlGJ4MH+H1fyZiHh3eN6qg2WmoclOEPfYATSuEa+dQT6wfgVpXlQ=="],
|
"expo-linking": ["expo-linking@56.0.15", "", { "dependencies": { "expo-constants": "~56.0.20", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-V3tkCxHIWO624ayw2L5Nfi0/bPgYy/0MgmoQR7g3FAKF4nyAayK8IhgAInz4MZ5Ao5Bb+5uZWDdUaS4yyEb+Gw=="],
|
||||||
|
|
||||||
"expo-localization": ["expo-localization@56.0.6", "", { "dependencies": { "rtl-detect": "^1.0.2" }, "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-zzBVoUFHCVNBywcxGsspoZeIXebihOo/AnmQYE4jMv8gHCSKlLNFT+ft+0+mWcZCMs9necvUs8S8TDonAu/xBA=="],
|
"expo-localization": ["expo-localization@56.0.6", "", { "dependencies": { "rtl-detect": "^1.0.2" }, "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-zzBVoUFHCVNBywcxGsspoZeIXebihOo/AnmQYE4jMv8gHCSKlLNFT+ft+0+mWcZCMs9necvUs8S8TDonAu/xBA=="],
|
||||||
|
|
||||||
"expo-location": ["expo-location@56.0.18", "", { "dependencies": { "@expo/image-utils": "^0.10.1" }, "peerDependencies": { "expo": "*" } }, "sha512-6xP0UwGy8a7EEHAMeigYAp3HNo3yWHAg05tVPUfwrOWepWPpFXmjsfUBUxQdkpfpjddJ9r+f4PplxZqKI0LtjA=="],
|
"expo-location": ["expo-location@56.0.21", "", { "dependencies": { "@expo/image-utils": "^0.10.3" }, "peerDependencies": { "expo": "*" } }, "sha512-HTwhlF9eWwRI/LSI0JW08Y96aYfcGpScbEn7xIH4PgxUC80kHOHI5CP0v/GlkIzT2Qhfr6cP9Jw9wnPdBfV//g=="],
|
||||||
|
|
||||||
"expo-manifests": ["expo-manifests@56.0.4", "", { "dependencies": { "expo-json-utils": "~56.0.0" }, "peerDependencies": { "expo": "*" } }, "sha512-Fokawl2UkiExIF0bqGoblRFA8lYpROVD+EpvDwSW4LgqQyPwNua1gLSgHZjdl5GsVugfRMMWE3LHaibDyX93hw=="],
|
"expo-manifests": ["expo-manifests@56.0.4", "", { "dependencies": { "expo-json-utils": "~56.0.0" }, "peerDependencies": { "expo": "*" } }, "sha512-Fokawl2UkiExIF0bqGoblRFA8lYpROVD+EpvDwSW4LgqQyPwNua1gLSgHZjdl5GsVugfRMMWE3LHaibDyX93hw=="],
|
||||||
|
|
||||||
"expo-modules-autolinking": ["expo-modules-autolinking@56.0.16", "", { "dependencies": { "@expo/require-utils": "^56.1.3", "@expo/spawn-async": "^1.8.0", "chalk": "^4.1.0", "commander": "^7.2.0" }, "bin": { "expo-modules-autolinking": "bin/expo-modules-autolinking.js" } }, "sha512-9JnL4N46P8ubDpDIfWolDn7nxU2j1rY67xY/dNVuyH0m+HG+r/JI16VYtjIf4COpZtEuFo4D3h3MBeFzGucMnw=="],
|
"expo-modules-autolinking": ["expo-modules-autolinking@56.0.20", "", { "dependencies": { "@expo/require-utils": "^56.1.5", "@expo/spawn-async": "^1.8.0", "chalk": "^4.1.0", "commander": "^7.2.0" }, "bin": { "expo-modules-autolinking": "bin/expo-modules-autolinking.js" } }, "sha512-bIUaHtWlqQpculjhN1wERVBoVeRCefkW3zXY/SqiXSm/6uofZy3NRF1t5Co44lmBQczAs/lFR7BwA8lmUiGMnw=="],
|
||||||
|
|
||||||
"expo-modules-core": ["expo-modules-core@56.0.17", "", { "dependencies": { "@expo/expo-modules-macros-plugin": "0.2.2", "expo-modules-jsi": "~56.0.10", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-worklets": "^0.7.4 || ^0.8.0" }, "optionalPeers": ["react-native-worklets"] }, "sha512-5J8whnT7Ccp+BrFClLmpF76omBqn95VZExroTm01Dgjm4vpty1Rb7U3we+ZUceNHtRd07Lw30u7FNfDgIhEbRQ=="],
|
"expo-modules-core": ["expo-modules-core@56.0.21", "", { "dependencies": { "@expo/expo-modules-macros-plugin": "0.2.2", "expo-modules-jsi": "~56.0.12", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-worklets": "^0.7.4 || ^0.8.0" }, "optionalPeers": ["react-native-worklets"] }, "sha512-lT413DZFuX/cSsOqGtZnqtyCGlY3F3FFDiVhwzB8wX4Ei9HZYPXhV59Mm8SNJquo8+JfLRi3arv3z5U4MqJRcA=="],
|
||||||
|
|
||||||
"expo-modules-jsi": ["expo-modules-jsi@56.0.10", "", { "peerDependencies": { "react-native": "*" } }, "sha512-fHZcFpYO/o62GYa6fJyAQJZcAShzhoN0iMMDzbr7vD3ewET6e1vAlTonbEakN9F0VHEgBFJ4NREy87uwVcpCuA=="],
|
"expo-modules-jsi": ["expo-modules-jsi@56.0.12", "", { "peerDependencies": { "react-native": "*" } }, "sha512-OnXiNbXzYybZPh5QnJyIIwQjQfN7PP4Di/2Hz0xQHKLgQmCfn/zAQziOjbUXVeL3cAyZ8+uDnTDYmDAc3B2qhQ=="],
|
||||||
|
|
||||||
"expo-notifications": ["expo-notifications@56.0.18", "", { "dependencies": { "@expo/image-utils": "^0.10.1", "abort-controller": "^3.0.0", "badgin": "^1.1.5", "expo-application": "~56.0.3", "expo-constants": "~56.0.18" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-HHnrwyCLC5srFojcHYS2KskbNroy9o2fwPKdyhjrdjjrBu4sNRKm4LepcuZjDy98cZKEm89WIPW8O45vut8Rgw=="],
|
"expo-notifications": ["expo-notifications@56.0.21", "", { "dependencies": { "@expo/image-utils": "^0.10.3", "abort-controller": "^3.0.0", "badgin": "^1.1.5", "expo-application": "~56.0.3", "expo-constants": "~56.0.21" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-j1rczqoAj0oBZYzcckMyILlJSLQdqOJuh7YzAckzTHkMuIPa7H0SF6IwcFIig1Tdqjku3aONqct6bbscY+qznw=="],
|
||||||
|
|
||||||
"expo-router": ["expo-router@56.2.11", "", { "dependencies": { "@expo/log-box": "^56.0.13", "@expo/metro-runtime": "^56.0.15", "@expo/schema-utils": "^56.0.0", "@expo/ui": "^56.0.18", "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.12", "@react-native-masked-view/masked-view": "^0.3.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "client-only": "^0.0.1", "color": "^4.2.3", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "expo-glass-effect": "^56.0.4", "expo-server": "^56.0.5", "expo-symbols": "^56.0.6", "fast-deep-equal": "^3.1.3", "invariant": "^2.2.4", "nanoid": "^3.3.8", "query-string": "^7.1.3", "react-fast-compare": "^3.2.2", "react-is": "^19.1.0", "react-native-drawer-layout": "^4.2.2", "react-native-screens": "^4.25.2", "server-only": "^0.0.1", "sf-symbols-typescript": "^2.1.0", "shallowequal": "^1.1.0", "standard-navigation": "^0.0.5", "vaul": "^1.1.2" }, "peerDependencies": { "@testing-library/react-native": ">= 13.2.0", "expo": "*", "expo-constants": "^56.0.18", "expo-linking": "^56.0.14", "react": "*", "react-dom": "*", "react-native": "*", "react-native-gesture-handler": "*", "react-native-reanimated": "*", "react-native-safe-area-context": ">= 5.4.0", "react-native-web": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "optionalPeers": ["@testing-library/react-native", "react-dom", "react-native-gesture-handler", "react-native-reanimated", "react-native-web", "react-server-dom-webpack"] }, "sha512-08DBTrKv3QanOc9u1JNxSEChW9c/qNFbQ0dO28OLvufWWfdSRkSdHmh365D2FgoZg1qaOzZPCDuL3tM6nGSfkQ=="],
|
"expo-router": ["expo-router@56.2.15", "", { "dependencies": { "@expo/log-box": "^56.0.14", "@expo/metro-runtime": "^56.0.17", "@expo/schema-utils": "^56.0.2", "@expo/ui": "^56.0.22", "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.12", "@react-native-masked-view/masked-view": "^0.3.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "client-only": "^0.0.1", "color": "^4.2.3", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "expo-glass-effect": "^56.0.4", "expo-server": "^56.0.5", "expo-symbols": "^56.0.6", "fast-deep-equal": "^3.1.3", "invariant": "^2.2.4", "nanoid": "^3.3.8", "query-string": "^7.1.3", "react-fast-compare": "^3.2.2", "react-is": "^19.1.0", "react-native-drawer-layout": "^4.2.2", "react-native-screens": "^4.25.2", "server-only": "^0.0.1", "sf-symbols-typescript": "^2.1.0", "shallowequal": "^1.1.0", "standard-navigation": "^0.0.5", "vaul": "^1.1.2" }, "peerDependencies": { "@testing-library/react-native": ">= 13.2.0", "expo": "*", "expo-constants": "^56.0.21", "expo-linking": "^56.0.15", "react": "*", "react-dom": "*", "react-native": "*", "react-native-gesture-handler": "*", "react-native-reanimated": "*", "react-native-safe-area-context": ">= 5.4.0", "react-native-web": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "optionalPeers": ["@testing-library/react-native", "react-dom", "react-native-gesture-handler", "react-native-reanimated", "react-native-web", "react-server-dom-webpack"] }, "sha512-VdPIBuJ0D2cv4gCulU5slhDlF9ocDNXsne03qu5AcTYROLIctlYDAzzLax7lSKZLP6GI6+Xw+I2dN7QMjC9UCA=="],
|
||||||
|
|
||||||
"expo-screen-orientation": ["expo-screen-orientation@56.0.5", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-Puf4L/cgM8z45Z2fwZzJtlVGSk0ZM/l3gBqXm50bKTACmUk8P8fr7HVbDfs8reyoZuEKKFZJ0VlnKo5i6cSotQ=="],
|
"expo-screen-orientation": ["expo-screen-orientation@56.0.5", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-Puf4L/cgM8z45Z2fwZzJtlVGSk0ZM/l3gBqXm50bKTACmUk8P8fr7HVbDfs8reyoZuEKKFZJ0VlnKo5i6cSotQ=="],
|
||||||
|
|
||||||
@@ -1057,9 +1065,9 @@
|
|||||||
|
|
||||||
"expo-server": ["expo-server@56.0.5", "", {}, "sha512-SmM2p2g3Jrktpiazcst+OxhjSzOHXKAY4BPURHYHXvApzzoybMmrNF4IEZ8DKZ145BhSe4ydAmlEFCRTsdtgUQ=="],
|
"expo-server": ["expo-server@56.0.5", "", {}, "sha512-SmM2p2g3Jrktpiazcst+OxhjSzOHXKAY4BPURHYHXvApzzoybMmrNF4IEZ8DKZ145BhSe4ydAmlEFCRTsdtgUQ=="],
|
||||||
|
|
||||||
"expo-sharing": ["expo-sharing@56.0.18", "", { "dependencies": { "@expo/config-plugins": "^56.0.9", "@expo/config-types": "^56.0.6", "@expo/plist": "^0.7.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-45w4BWNFmdTczp+fJX6YfwJrn9sX+VeRWz2VWLhauygcCrym44HtVDXX5yVYPB9TW9ZesLcEI+CCrCBNWL7smQ=="],
|
"expo-sharing": ["expo-sharing@56.0.22", "", { "dependencies": { "@expo/config-plugins": "^56.0.13", "@expo/config-types": "^56.0.7", "@expo/plist": "^0.7.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-r2lt8sJUbEi6O1DIK192GEk6JayBEQNjfTvJ3iHzW//5dUy20NCaLSKFeE2z+DE+lVzu5izOocWRwIbbubywvw=="],
|
||||||
|
|
||||||
"expo-splash-screen": ["expo-splash-screen@56.0.10", "", { "dependencies": { "@expo/config-plugins": "~56.0.8", "@expo/image-utils": "^0.10.1", "xml2js": "0.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-vDIlo8hzt9HlCZQ0kSY66v83D1WEXOJbVMeyPDfXDu9tbDdPMNUyDpi4WGJXikAjxnAKfbt5Mv5NnEbxINy+VA=="],
|
"expo-splash-screen": ["expo-splash-screen@56.0.13", "", { "dependencies": { "@expo/config-plugins": "~56.0.13", "@expo/image-utils": "^0.10.3", "xml2js": "0.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-8+ytfHX4/B5sj97N4PIzm4IP/7uDbbYTsY/vOMWa7ASTVkGZB+pXB23dBwBu7TcKx5w9Wt0NkaPAwy6dhNEdag=="],
|
||||||
|
|
||||||
"expo-status-bar": ["expo-status-bar@56.0.4", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-IGs/fDfkHXofy2ZQrGiXayhFK04HB85FZXorhcEhDZEcqASKgSqpak+HwUtAaR0MeTJwWyHNF7I6VmVbbp8EcA=="],
|
"expo-status-bar": ["expo-status-bar@56.0.4", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-IGs/fDfkHXofy2ZQrGiXayhFK04HB85FZXorhcEhDZEcqASKgSqpak+HwUtAaR0MeTJwWyHNF7I6VmVbbp8EcA=="],
|
||||||
|
|
||||||
@@ -1067,7 +1075,7 @@
|
|||||||
|
|
||||||
"expo-system-ui": ["expo-system-ui@56.0.5", "", { "dependencies": { "@react-native/normalize-colors": "0.85.3", "debug": "^4.3.2" }, "peerDependencies": { "expo": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-n1MmnUArV4cc3gVed9fGtluPme00PE9axKVx+NHbKxHFMam5l4GcOI7PxbYKFNx8o7WA1LRD7eLW33agmZrxGg=="],
|
"expo-system-ui": ["expo-system-ui@56.0.5", "", { "dependencies": { "@react-native/normalize-colors": "0.85.3", "debug": "^4.3.2" }, "peerDependencies": { "expo": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-n1MmnUArV4cc3gVed9fGtluPme00PE9axKVx+NHbKxHFMam5l4GcOI7PxbYKFNx8o7WA1LRD7eLW33agmZrxGg=="],
|
||||||
|
|
||||||
"expo-task-manager": ["expo-task-manager@56.0.19", "", { "dependencies": { "unimodules-app-loader": "~56.0.0" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-eDeBR8vXlOAXBck29AzPl+tv4QlJus+cgkQ0TGOBqAlgRPWPWd7ebhqrhb0KRieUZIvzlycfcxjHR/+AiEiMHA=="],
|
"expo-task-manager": ["expo-task-manager@56.0.22", "", { "dependencies": { "unimodules-app-loader": "~56.0.0" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-EtYC7o5phj1nGQbdXwSF1fIm9Ws5oP/+wCMWS/Tl15OS3vrsG1INEyptKCNVoEEuXZ15wdhSlmp7J4Y2P5uO/A=="],
|
||||||
|
|
||||||
"expo-updates-interface": ["expo-updates-interface@56.0.2", "", { "peerDependencies": { "expo": "*" } }, "sha512-eWTwSZ9y8vrULG2oBn2TQSSIwBGSq/TxGJ3jY6tuVS2FWH/ASRIiKs3zkUZTRoC3ZuV2alz0mUClYV7nNrFx8g=="],
|
"expo-updates-interface": ["expo-updates-interface@56.0.2", "", { "peerDependencies": { "expo": "*" } }, "sha512-eWTwSZ9y8vrULG2oBn2TQSSIwBGSq/TxGJ3jY6tuVS2FWH/ASRIiKs3zkUZTRoC3ZuV2alz0mUClYV7nNrFx8g=="],
|
||||||
|
|
||||||
@@ -1081,9 +1089,9 @@
|
|||||||
|
|
||||||
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
||||||
|
|
||||||
"fast-xml-builder": ["fast-xml-builder@1.2.0", "", { "dependencies": { "path-expression-matcher": "^1.5.0", "xml-naming": "^0.1.0" } }, "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q=="],
|
"fast-xml-builder": ["fast-xml-builder@1.3.0", "", { "dependencies": { "path-expression-matcher": "^1.6.2", "xml-naming": "^0.3.0" } }, "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ=="],
|
||||||
|
|
||||||
"fast-xml-parser": ["fast-xml-parser@5.9.0", "", { "dependencies": { "@nodable/entities": "^2.2.0", "fast-xml-builder": "^1.2.0", "is-unsafe": "^1.0.1", "path-expression-matcher": "^1.5.0", "strnum": "^2.4.0", "xml-naming": "^0.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-duBuXbyIhEeNO4GjFuVqr0nF047oNwr18aum+zJyqo0MUG/n7Afgs3Qv3D6VN3ONedUKxiuFlPiMGIa0Z11chA=="],
|
"fast-xml-parser": ["fast-xml-parser@5.10.1", "", { "dependencies": { "@nodable/entities": "^3.0.0", "fast-xml-builder": "^1.2.0", "is-unsafe": "^2.0.0", "path-expression-matcher": "^1.6.2", "strnum": "^2.4.1", "xml-naming": "^0.3.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-IEMIf7298kXuZSRFoGfMYrl7is8LpavODgbNz1cwIudv7KwVFnuU+UsMporfq6PD6aXSlawZlARiA3UywCTfMw=="],
|
||||||
|
|
||||||
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
|
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
|
||||||
|
|
||||||
@@ -1187,9 +1195,9 @@
|
|||||||
|
|
||||||
"hyphenate-style-name": ["hyphenate-style-name@1.1.0", "", {}, "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw=="],
|
"hyphenate-style-name": ["hyphenate-style-name@1.1.0", "", {}, "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw=="],
|
||||||
|
|
||||||
"i18next": ["i18next@26.3.1", "", { "peerDependencies": { "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-txQqd5EULsqEh9OJqRH15aCaOuy/nLJyhw5EHCSKLKJE1aBbb3Zve2+uQIxgWhPm1QqUQoWyQBm2kfmmIrzkcQ=="],
|
"i18next": ["i18next@26.3.6", "", { "peerDependencies": { "typescript": "^5 || ^6 || ^7" }, "optionalPeers": ["typescript"] }, "sha512-Bu5Z2nAXgfVyM8xvW3jk9EKRIuX37PudsrBViThNFx7CR7aaYTpP01cxNB/E4c4UUzTDiAZRstEhsRfPOL/8xA=="],
|
||||||
|
|
||||||
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
"iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="],
|
||||||
|
|
||||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||||
|
|
||||||
@@ -1231,7 +1239,7 @@
|
|||||||
|
|
||||||
"is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
|
"is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
|
||||||
|
|
||||||
"is-unsafe": ["is-unsafe@1.0.1", "", {}, "sha512-CLK2+VdgERgD96EYm5lUQssZYlRg2tkZnbsxZoacmSiRxiFJ4Nk4SzjCl+Ur+v3kXIY9dTIdb3IH22y1mZ56LA=="],
|
"is-unsafe": ["is-unsafe@2.0.0", "", {}, "sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA=="],
|
||||||
|
|
||||||
"is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
|
"is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
|
||||||
|
|
||||||
@@ -1269,7 +1277,7 @@
|
|||||||
|
|
||||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||||
|
|
||||||
"js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="],
|
"js-yaml": ["js-yaml@4.3.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q=="],
|
||||||
|
|
||||||
"jsc-safe-url": ["jsc-safe-url@0.2.4", "", {}, "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q=="],
|
"jsc-safe-url": ["jsc-safe-url@0.2.4", "", {}, "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q=="],
|
||||||
|
|
||||||
@@ -1327,7 +1335,7 @@
|
|||||||
|
|
||||||
"lint-staged": ["lint-staged@17.0.8", "", { "dependencies": { "listr2": "^10.2.1", "picomatch": "^4.0.4", "string-argv": "^0.3.2", "tinyexec": "^1.2.4" }, "optionalDependencies": { "yaml": "^2.9.0" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-B2P/d+jVW0UXOQ0MVMLrB/9ydA1P+zz6jYfdrbbEd9ur3S2rcbduFWKiUCC02Sm5hbC8nrm7y24WuYMG54HfxA=="],
|
"lint-staged": ["lint-staged@17.0.8", "", { "dependencies": { "listr2": "^10.2.1", "picomatch": "^4.0.4", "string-argv": "^0.3.2", "tinyexec": "^1.2.4" }, "optionalDependencies": { "yaml": "^2.9.0" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-B2P/d+jVW0UXOQ0MVMLrB/9ydA1P+zz6jYfdrbbEd9ur3S2rcbduFWKiUCC02Sm5hbC8nrm7y24WuYMG54HfxA=="],
|
||||||
|
|
||||||
"listr2": ["listr2@10.2.1", "", { "dependencies": { "cli-truncate": "^5.2.0", "eventemitter3": "^5.0.4", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^10.0.0" } }, "sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q=="],
|
"listr2": ["listr2@10.2.2", "", { "dependencies": { "cli-truncate": "^5.2.0", "eventemitter3": "^5.0.4", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^10.0.0" } }, "sha512-JtNtbZj8q5BnDMR7trpwvwk3RIrANtIVzEUm8w7amp6xelLgyuq+4WZoTH913XaQAoH/cNdYhaNzBPA2U3xbDw=="],
|
||||||
|
|
||||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||||
|
|
||||||
@@ -1421,7 +1429,7 @@
|
|||||||
|
|
||||||
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
|
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
|
||||||
|
|
||||||
"nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="],
|
"nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],
|
||||||
|
|
||||||
"nativewind": ["nativewind@2.0.11", "", { "dependencies": { "@babel/generator": "^7.18.7", "@babel/helper-module-imports": "7.18.6", "@babel/types": "7.19.0", "css-mediaquery": "^0.1.2", "css-to-react-native": "^3.0.0", "micromatch": "^4.0.5", "postcss": "^8.4.12", "postcss-calc": "^8.2.4", "postcss-color-functional-notation": "^4.2.2", "postcss-css-variables": "^0.18.0", "postcss-nested": "^5.0.6", "react-is": "^18.1.0", "use-sync-external-store": "^1.1.0" }, "peerDependencies": { "tailwindcss": "~3" } }, "sha512-qCEXUwKW21RYJ33KRAJl3zXq2bCq82WoI564fI21D/TiqhfmstZOqPN53RF8qK1NDK6PGl56b2xaTxgObEePEg=="],
|
"nativewind": ["nativewind@2.0.11", "", { "dependencies": { "@babel/generator": "^7.18.7", "@babel/helper-module-imports": "7.18.6", "@babel/types": "7.19.0", "css-mediaquery": "^0.1.2", "css-to-react-native": "^3.0.0", "micromatch": "^4.0.5", "postcss": "^8.4.12", "postcss-calc": "^8.2.4", "postcss-color-functional-notation": "^4.2.2", "postcss-css-variables": "^0.18.0", "postcss-nested": "^5.0.6", "react-is": "^18.1.0", "use-sync-external-store": "^1.1.0" }, "peerDependencies": { "tailwindcss": "~3" } }, "sha512-qCEXUwKW21RYJ33KRAJl3zXq2bCq82WoI564fI21D/TiqhfmstZOqPN53RF8qK1NDK6PGl56b2xaTxgObEePEg=="],
|
||||||
|
|
||||||
@@ -1435,7 +1443,7 @@
|
|||||||
|
|
||||||
"node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
|
"node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
|
||||||
|
|
||||||
"node-releases": ["node-releases@2.0.47", "", {}, "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og=="],
|
"node-releases": ["node-releases@2.0.51", "", {}, "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ=="],
|
||||||
|
|
||||||
"node-stream-zip": ["node-stream-zip@1.15.0", "", {}, "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw=="],
|
"node-stream-zip": ["node-stream-zip@1.15.0", "", {}, "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw=="],
|
||||||
|
|
||||||
@@ -1495,7 +1503,7 @@
|
|||||||
|
|
||||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||||
|
|
||||||
"path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="],
|
"path-expression-matcher": ["path-expression-matcher@1.6.2", "", {}, "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ=="],
|
||||||
|
|
||||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||||
|
|
||||||
@@ -1507,7 +1515,7 @@
|
|||||||
|
|
||||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
"picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
|
||||||
|
|
||||||
"pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
|
"pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
|
||||||
|
|
||||||
@@ -1519,7 +1527,7 @@
|
|||||||
|
|
||||||
"pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="],
|
"pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="],
|
||||||
|
|
||||||
"postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="],
|
"postcss": ["postcss@8.5.19", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ=="],
|
||||||
|
|
||||||
"postcss-calc": ["postcss-calc@8.2.4", "", { "dependencies": { "postcss-selector-parser": "^6.0.9", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2.2" } }, "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q=="],
|
"postcss-calc": ["postcss-calc@8.2.4", "", { "dependencies": { "postcss-selector-parser": "^6.0.9", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.2.2" } }, "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q=="],
|
||||||
|
|
||||||
@@ -1559,7 +1567,7 @@
|
|||||||
|
|
||||||
"qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="],
|
"qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="],
|
||||||
|
|
||||||
"qs": ["qs@6.15.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw=="],
|
"qs": ["qs@6.15.3", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A=="],
|
||||||
|
|
||||||
"query-string": ["query-string@7.1.3", "", { "dependencies": { "decode-uri-component": "^0.2.2", "filter-obj": "^1.1.0", "split-on-first": "^1.0.0", "strict-uri-encode": "^2.0.0" } }, "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg=="],
|
"query-string": ["query-string@7.1.3", "", { "dependencies": { "decode-uri-component": "^0.2.2", "filter-obj": "^1.1.0", "split-on-first": "^1.0.0", "strict-uri-encode": "^2.0.0" } }, "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg=="],
|
||||||
|
|
||||||
@@ -1589,7 +1597,7 @@
|
|||||||
|
|
||||||
"react-native-awesome-slider": ["react-native-awesome-slider@2.9.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-gesture-handler": ">=2.0.0", "react-native-reanimated": ">=3.0.0" } }, "sha512-sc5qgX4YtM6IxjtosjgQLdsal120MvU+YWs0F2MdgQWijps22AXLDCUoBnZZ8vxVhVyJ2WnnIPrmtVBvVJjSuQ=="],
|
"react-native-awesome-slider": ["react-native-awesome-slider@2.9.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-gesture-handler": ">=2.0.0", "react-native-reanimated": ">=3.0.0" } }, "sha512-sc5qgX4YtM6IxjtosjgQLdsal120MvU+YWs0F2MdgQWijps22AXLDCUoBnZZ8vxVhVyJ2WnnIPrmtVBvVJjSuQ=="],
|
||||||
|
|
||||||
"react-native-bottom-tabs": ["react-native-bottom-tabs@1.2.0", "", { "dependencies": { "react-freeze": "^1.0.0", "sf-symbols-typescript": "^2.0.0", "use-latest-callback": "^0.2.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-ScVPko86ts+m6JMNtI24MCSYJCOZc1aZkn9qwS9ly3o0ubajRWDpCzgRJfRFysi08bKrcqAXKVCHZNHvNb2PTA=="],
|
"react-native-bottom-tabs": ["react-native-bottom-tabs@1.4.0", "", { "dependencies": { "react-freeze": "^1.0.0", "sf-symbols-typescript": "^2.0.0", "use-latest-callback": "^0.2.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-XGuKHGU2FH1j/Ii7HzFE0+6ejiLs4mrcBRQRJff2g94hRjDZnx+trB+cOFG6z1BHGPLv6CD833S0m+nQuZB9nA=="],
|
||||||
|
|
||||||
"react-native-circular-progress": ["react-native-circular-progress@1.4.1", "", { "dependencies": { "prop-types": "^15.8.1" }, "peerDependencies": { "react": ">=16.0.0", "react-native": ">=0.50.0", "react-native-svg": ">=7.0.0" } }, "sha512-HEzvI0WPuWvsCgWE3Ff2HBTMgAEQB2GvTFw0KHyD/t1STAlDDRiolu0mEGhVvihKR3jJu3v3V4qzvSklY/7XzQ=="],
|
"react-native-circular-progress": ["react-native-circular-progress@1.4.1", "", { "dependencies": { "prop-types": "^15.8.1" }, "peerDependencies": { "react": ">=16.0.0", "react-native": ">=0.50.0", "react-native-svg": ">=7.0.0" } }, "sha512-HEzvI0WPuWvsCgWE3Ff2HBTMgAEQB2GvTFw0KHyD/t1STAlDDRiolu0mEGhVvihKR3jJu3v3V4qzvSklY/7XzQ=="],
|
||||||
|
|
||||||
@@ -1601,7 +1609,7 @@
|
|||||||
|
|
||||||
"react-native-draggable-flatlist": ["react-native-draggable-flatlist@4.0.3", "", { "dependencies": { "@babel/preset-typescript": "^7.17.12" }, "peerDependencies": { "react-native": ">=0.64.0", "react-native-gesture-handler": ">=2.0.0", "react-native-reanimated": ">=2.8.0" } }, "sha512-2F4x5BFieWdGq9SetD2nSAR7s7oQCSgNllYgERRXXtNfSOuAGAVbDb/3H3lP0y5f7rEyNwabKorZAD/SyyNbDw=="],
|
"react-native-draggable-flatlist": ["react-native-draggable-flatlist@4.0.3", "", { "dependencies": { "@babel/preset-typescript": "^7.17.12" }, "peerDependencies": { "react-native": ">=0.64.0", "react-native-gesture-handler": ">=2.0.0", "react-native-reanimated": ">=2.8.0" } }, "sha512-2F4x5BFieWdGq9SetD2nSAR7s7oQCSgNllYgERRXXtNfSOuAGAVbDb/3H3lP0y5f7rEyNwabKorZAD/SyyNbDw=="],
|
||||||
|
|
||||||
"react-native-drawer-layout": ["react-native-drawer-layout@4.2.5", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*", "react-native-gesture-handler": ">= 2.0.0", "react-native-reanimated": ">= 2.0.0" } }, "sha512-Yl82uLkXjXuq7222hWGIDsq5A6R/bsCeCEgdIxQUxAEHf00oRdDnRByLx3Fsij3qwtmYNPGrHV1NH8G8hbCbLQ=="],
|
"react-native-drawer-layout": ["react-native-drawer-layout@4.2.8", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*", "react-native-gesture-handler": ">= 2.0.0", "react-native-reanimated": ">= 2.0.0" } }, "sha512-IswXoXEaQAnzUGo7tJyMPHP0qyUc/7zufpG+kpiStYegHpKQBBnlQoRdXkL1HbCa2o1ROWDhCSJ8XS0V4LNcjQ=="],
|
||||||
|
|
||||||
"react-native-edge-to-edge": ["react-native-edge-to-edge@1.8.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-bhvsKqeX9PGkY9wBUk9vni/tJNJdKtLPbs/j3e/3CdV4JmUWfTXYYoL+4Hc8Wmej+5eJxkc8KOFa454ruFWBCA=="],
|
"react-native-edge-to-edge": ["react-native-edge-to-edge@1.8.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-bhvsKqeX9PGkY9wBUk9vni/tJNJdKtLPbs/j3e/3CdV4JmUWfTXYYoL+4Hc8Wmej+5eJxkc8KOFa454ruFWBCA=="],
|
||||||
|
|
||||||
@@ -1653,7 +1661,7 @@
|
|||||||
|
|
||||||
"react-native-web": ["react-native-web@0.21.2", "", { "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", "fbjs": "^3.0.4", "inline-style-prefixer": "^7.0.1", "memoize-one": "^6.0.0", "nullthrows": "^1.1.1", "postcss-value-parser": "^4.2.0", "styleq": "^0.1.3" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg=="],
|
"react-native-web": ["react-native-web@0.21.2", "", { "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", "fbjs": "^3.0.4", "inline-style-prefixer": "^7.0.1", "memoize-one": "^6.0.0", "nullthrows": "^1.1.1", "postcss-value-parser": "^4.2.0", "styleq": "^0.1.3" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg=="],
|
||||||
|
|
||||||
"react-native-worklets": ["react-native-worklets@0.11.0", "", { "dependencies": { "@babel/generator": "^7.27.1", "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-class-properties": "^7.28.6", "@babel/plugin-transform-classes": "^7.28.6", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/preset-typescript": "^7.28.5", "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1", "convert-source-map": "^2.0.0", "semver": "^7.7.4" }, "peerDependencies": { "@babel/core": "*", "@react-native/metro-config": "*", "react": "*", "react-native": "0.83 - 0.86" } }, "sha512-mNbNxqbeGUEPKH8o15wieKH6+OxNikWIV/LHlP8qU6YD+FkA8reUA98ptDVboNCtCfiE/duEIZO+PC0qsHxC3Q=="],
|
"react-native-worklets": ["react-native-worklets@0.8.3", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-class-properties": "^7.27.1", "@babel/plugin-transform-classes": "^7.28.4", "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", "@babel/plugin-transform-optional-chaining": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/preset-typescript": "^7.27.1", "convert-source-map": "^2.0.0", "semver": "^7.7.3" }, "peerDependencies": { "@babel/core": "*", "@react-native/metro-config": "*", "react": "*", "react-native": "0.81 - 0.85" } }, "sha512-oCBJROyLU7yG/1R8s0INMflygTH71bx+5XcYkH0CM938TlhSoVbiunE1WVW5FZa51vwYqfLie/IXMX2s1Kh3eg=="],
|
||||||
|
|
||||||
"react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],
|
"react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],
|
||||||
|
|
||||||
@@ -1741,7 +1749,7 @@
|
|||||||
|
|
||||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||||
|
|
||||||
"shell-quote": ["shell-quote@1.8.4", "", {}, "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ=="],
|
"shell-quote": ["shell-quote@1.10.0", "", {}, "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA=="],
|
||||||
|
|
||||||
"side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="],
|
"side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="],
|
||||||
|
|
||||||
@@ -1781,7 +1789,7 @@
|
|||||||
|
|
||||||
"stacktrace-parser": ["stacktrace-parser@0.1.11", "", { "dependencies": { "type-fest": "^0.7.1" } }, "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg=="],
|
"stacktrace-parser": ["stacktrace-parser@0.1.11", "", { "dependencies": { "type-fest": "^0.7.1" } }, "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg=="],
|
||||||
|
|
||||||
"standard-navigation": ["standard-navigation@0.0.7", "", {}, "sha512-NCGLCNyuXrFOkGHxdNZFnpsehGtiq1oXbPhKl7ZuxFO5J//H2evqqOchmD4YwEUJnkjO4kH9Xp4hQX6hdAYCKQ=="],
|
"standard-navigation": ["standard-navigation@0.0.8", "", { "peerDependencies": { "react": "*" } }, "sha512-TyVbo7INUDWtsUWDFn8RR7kwR87U0S4xHfLfbbnyeC581TmmyqQ+eM+nPw8rQTSD8QitRVcYfPaSHr/QJiUy1g=="],
|
||||||
|
|
||||||
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
||||||
|
|
||||||
@@ -1803,7 +1811,7 @@
|
|||||||
|
|
||||||
"strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
|
"strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
|
||||||
|
|
||||||
"strnum": ["strnum@2.4.0", "", { "dependencies": { "anynum": "^1.0.0" } }, "sha512-sHrVyWWdq28RbhjuJdZsA1SnGRJV6NiXbk6AXBxDOsgAcA+lmpUZCYjOdLBxkXMwis6RRe7dlZt4VlIWFVzkmg=="],
|
"strnum": ["strnum@2.4.1", "", { "dependencies": { "anynum": "^1.0.1" } }, "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg=="],
|
||||||
|
|
||||||
"strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="],
|
"strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="],
|
||||||
|
|
||||||
@@ -1825,7 +1833,7 @@
|
|||||||
|
|
||||||
"terminal-link": ["terminal-link@2.1.1", "", { "dependencies": { "ansi-escapes": "^4.2.1", "supports-hyperlinks": "^2.0.0" } }, "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ=="],
|
"terminal-link": ["terminal-link@2.1.1", "", { "dependencies": { "ansi-escapes": "^4.2.1", "supports-hyperlinks": "^2.0.0" } }, "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ=="],
|
||||||
|
|
||||||
"terser": ["terser@5.48.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q=="],
|
"terser": ["terser@5.49.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA=="],
|
||||||
|
|
||||||
"text-encoding": ["text-encoding@0.7.0", "", {}, "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA=="],
|
"text-encoding": ["text-encoding@0.7.0", "", {}, "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA=="],
|
||||||
|
|
||||||
@@ -1863,7 +1871,7 @@
|
|||||||
|
|
||||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
"tsx": ["tsx@4.22.4", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg=="],
|
"tsx": ["tsx@4.23.1", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ=="],
|
||||||
|
|
||||||
"type-fest": ["type-fest@0.7.1", "", {}, "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg=="],
|
"type-fest": ["type-fest@0.7.1", "", {}, "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg=="],
|
||||||
|
|
||||||
@@ -1941,11 +1949,11 @@
|
|||||||
|
|
||||||
"wrap-ansi": ["wrap-ansi@10.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "string-width": "^8.2.0", "strip-ansi": "^7.1.2" } }, "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ=="],
|
"wrap-ansi": ["wrap-ansi@10.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "string-width": "^8.2.0", "strip-ansi": "^7.1.2" } }, "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ=="],
|
||||||
|
|
||||||
"ws": ["ws@7.5.11", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA=="],
|
"ws": ["ws@7.5.13", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA=="],
|
||||||
|
|
||||||
"xcode": ["xcode@3.0.1", "", { "dependencies": { "simple-plist": "^1.1.0", "uuid": "^7.0.3" } }, "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA=="],
|
"xcode": ["xcode@3.0.1", "", { "dependencies": { "simple-plist": "^1.1.0", "uuid": "^7.0.3" } }, "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA=="],
|
||||||
|
|
||||||
"xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="],
|
"xml-naming": ["xml-naming@0.3.0", "", {}, "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ=="],
|
||||||
|
|
||||||
"xml2js": ["xml2js@0.6.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w=="],
|
"xml2js": ["xml2js@0.6.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w=="],
|
||||||
|
|
||||||
@@ -1957,7 +1965,7 @@
|
|||||||
|
|
||||||
"yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
|
"yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
|
||||||
|
|
||||||
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
"yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="],
|
||||||
|
|
||||||
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||||
|
|
||||||
@@ -1965,7 +1973,7 @@
|
|||||||
|
|
||||||
"zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
"zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
||||||
|
|
||||||
"zxing-wasm": ["zxing-wasm@3.1.0", "", { "dependencies": { "@types/emscripten": "^1.41.5", "type-fest": "^5.7.0" } }, "sha512-5+3V1wPRx4gvbeLH2jB7n2cKrYJ1q4i3QgjnBUtrDPeqxJSi6BdzKJg4y6aF6bgW8zfntnYJyrkqFMevDhL2NA=="],
|
"zxing-wasm": ["zxing-wasm@3.1.1", "", { "dependencies": { "@types/emscripten": "^1.41.5", "type-fest": "^5.8.0" } }, "sha512-g0sPJBIubO6zLcJh1jftLPIN6xziaqLsvLgtpGKwDrEhyXXqla3E3yjFrznlr78UHIOMzbJPi0HDWKs/KgaB7A=="],
|
||||||
|
|
||||||
"@babel/helper-module-transforms/@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="],
|
"@babel/helper-module-transforms/@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="],
|
||||||
|
|
||||||
@@ -1981,11 +1989,11 @@
|
|||||||
|
|
||||||
"@expo/cli/ora": ["ora@3.4.0", "", { "dependencies": { "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-spinners": "^2.0.0", "log-symbols": "^2.2.0", "strip-ansi": "^5.2.0", "wcwidth": "^1.0.1" } }, "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg=="],
|
"@expo/cli/ora": ["ora@3.4.0", "", { "dependencies": { "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-spinners": "^2.0.0", "log-symbols": "^2.2.0", "strip-ansi": "^5.2.0", "wcwidth": "^1.0.1" } }, "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg=="],
|
||||||
|
|
||||||
"@expo/cli/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
|
"@expo/cli/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@expo/cli/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
"@expo/cli/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||||
|
|
||||||
"@expo/cli/ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
|
"@expo/cli/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="],
|
||||||
|
|
||||||
"@expo/cli/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
"@expo/cli/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||||
|
|
||||||
@@ -1993,55 +2001,59 @@
|
|||||||
|
|
||||||
"@expo/config/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
|
"@expo/config/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
|
||||||
|
|
||||||
"@expo/config/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
|
"@expo/config/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@expo/config-plugins/getenv": ["getenv@2.0.0", "", {}, "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ=="],
|
"@expo/config-plugins/getenv": ["getenv@2.0.0", "", {}, "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ=="],
|
||||||
|
|
||||||
"@expo/config-plugins/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
|
"@expo/config-plugins/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
|
||||||
|
|
||||||
"@expo/config-plugins/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
|
"@expo/config-plugins/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@expo/devcert/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
"@expo/devcert/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||||
|
|
||||||
"@expo/env/getenv": ["getenv@2.0.0", "", {}, "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ=="],
|
"@expo/env/getenv": ["getenv@2.0.0", "", {}, "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ=="],
|
||||||
|
|
||||||
|
"@expo/fingerprint/@expo/env": ["@expo/env@2.4.2", "", { "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", "getenv": "^2.0.0" } }, "sha512-28pqaEqwnmLduZ00Pq9HkSzE5wbj1MTwp5/n8nm8rD8MCjR9eUnVOwmNksPI3Be2ReAPO/DbPn1puy0mvoocsQ=="],
|
||||||
|
|
||||||
"@expo/fingerprint/getenv": ["getenv@2.0.0", "", {}, "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ=="],
|
"@expo/fingerprint/getenv": ["getenv@2.0.0", "", {}, "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ=="],
|
||||||
|
|
||||||
"@expo/fingerprint/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
|
"@expo/fingerprint/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
|
||||||
|
|
||||||
"@expo/fingerprint/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
|
"@expo/fingerprint/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@expo/image-utils/getenv": ["getenv@2.0.0", "", {}, "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ=="],
|
"@expo/image-utils/getenv": ["getenv@2.0.0", "", {}, "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ=="],
|
||||||
|
|
||||||
"@expo/image-utils/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
|
"@expo/image-utils/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@expo/metro-config/getenv": ["getenv@2.0.0", "", {}, "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ=="],
|
"@expo/metro-config/getenv": ["getenv@2.0.0", "", {}, "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ=="],
|
||||||
|
|
||||||
"@expo/metro-config/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
|
"@expo/metro-config/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
|
||||||
|
|
||||||
|
"@expo/package-manager/@expo/json-file": ["@expo/json-file@11.0.1", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "json5": "^2.2.3" } }, "sha512-zxHWj4MKKMAL29ZQSY/Fssx4Thluk40JmuGNaeS078wy/NhlFhnVi+rHHunulE3xJAJ0CM73m8VK2+GkF9eRwQ=="],
|
||||||
|
|
||||||
"@expo/package-manager/ora": ["ora@3.4.0", "", { "dependencies": { "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-spinners": "^2.0.0", "log-symbols": "^2.2.0", "strip-ansi": "^5.2.0", "wcwidth": "^1.0.1" } }, "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg=="],
|
"@expo/package-manager/ora": ["ora@3.4.0", "", { "dependencies": { "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-spinners": "^2.0.0", "log-symbols": "^2.2.0", "strip-ansi": "^5.2.0", "wcwidth": "^1.0.1" } }, "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg=="],
|
||||||
|
|
||||||
"@expo/prebuild-config/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
|
"@expo/prebuild-config/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@expo/ws-tunnel/ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
|
"@expo/ws-tunnel/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="],
|
||||||
|
|
||||||
"@jimp/png/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
|
"@jimp/png/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
|
||||||
|
|
||||||
"@react-native-community/cli/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
|
"@react-native-community/cli/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@react-native-community/cli-doctor/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
|
"@react-native-community/cli-doctor/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@react-native-community/cli-server-api/open": ["open@6.4.0", "", { "dependencies": { "is-wsl": "^1.1.0" } }, "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg=="],
|
"@react-native-community/cli-server-api/open": ["open@6.4.0", "", { "dependencies": { "is-wsl": "^1.1.0" } }, "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg=="],
|
||||||
|
|
||||||
"@react-native-community/cli-server-api/ws": ["ws@6.2.4", "", { "dependencies": { "async-limiter": "~1.0.0" } }, "sha512-PNIUUyLI5YpkJZj60YBzX1o0ByQ4ovvfmq9N/Kig/PAYbVlGyz4R6G0SEWrD0O9acc0sT2+IdMBVLFv8FSi0Nw=="],
|
"@react-native-community/cli-server-api/ws": ["ws@6.2.6", "", { "dependencies": { "async-limiter": "~1.0.0" } }, "sha512-XTrf1gv7kXoVf1hbC3PAyAiPgR8Wz1blcrYIjEsUmr08BLksT41R8KbjmS9408C2ERx7v1JDLD/BkpLEttjfKA=="],
|
||||||
|
|
||||||
"@react-native-community/cli-tools/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
|
"@react-native-community/cli-tools/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@react-native/babel-preset/@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.86.0", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@react-native/codegen": "0.86.0" } }, "sha512-qdsABWNW7uTll90l4Vh03gjeyu3WVDi2CyiiyvYGMRDcoYbjbQi6df3BMAm9lQI2yslZ1T14LlDDAsgTwNxplA=="],
|
"@react-native/babel-preset/@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.86.0", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@react-native/codegen": "0.86.0" } }, "sha512-qdsABWNW7uTll90l4Vh03gjeyu3WVDi2CyiiyvYGMRDcoYbjbQi6df3BMAm9lQI2yslZ1T14LlDDAsgTwNxplA=="],
|
||||||
|
|
||||||
"@react-native/babel-preset/babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.36.0", "", { "dependencies": { "hermes-parser": "0.36.0" } }, "sha512-LhD0xdoedDw7ansQgXbB2DADLZIK/LRXuWNBPuVzMc5S2WK5GyT89tCM+cQzxFGO0mGyLK6D5TrVOJJzAoDy8Q=="],
|
"@react-native/babel-preset/babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.36.0", "", { "dependencies": { "hermes-parser": "0.36.0" } }, "sha512-LhD0xdoedDw7ansQgXbB2DADLZIK/LRXuWNBPuVzMc5S2WK5GyT89tCM+cQzxFGO0mGyLK6D5TrVOJJzAoDy8Q=="],
|
||||||
|
|
||||||
"@react-native/community-cli-plugin/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
|
"@react-native/community-cli-plugin/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"@react-native/metro-babel-transformer/hermes-parser": ["hermes-parser@0.36.0", "", { "dependencies": { "hermes-estree": "0.36.0" } }, "sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w=="],
|
"@react-native/metro-babel-transformer/hermes-parser": ["hermes-parser@0.36.0", "", { "dependencies": { "hermes-estree": "0.36.0" } }, "sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w=="],
|
||||||
|
|
||||||
@@ -2075,12 +2087,10 @@
|
|||||||
|
|
||||||
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||||
|
|
||||||
"cli-truncate/string-width": ["string-width@8.2.1", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA=="],
|
"cli-truncate/string-width": ["string-width@8.2.2", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg=="],
|
||||||
|
|
||||||
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||||
|
|
||||||
"compressible/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
|
||||||
|
|
||||||
"compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
"compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||||
|
|
||||||
"connect/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
"connect/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||||
@@ -2089,7 +2099,7 @@
|
|||||||
|
|
||||||
"error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
|
"error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
|
||||||
|
|
||||||
"expo-build-properties/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
|
"expo-build-properties/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
|
"expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
|
||||||
|
|
||||||
@@ -2157,15 +2167,15 @@
|
|||||||
|
|
||||||
"nativewind/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
"nativewind/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
||||||
|
|
||||||
"npm-package-arg/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
|
"npm-package-arg/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"parse-png/pngjs": ["pngjs@3.4.0", "", {}, "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="],
|
"parse-png/pngjs": ["pngjs@3.4.0", "", {}, "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="],
|
||||||
|
|
||||||
"patch-package/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="],
|
"patch-package/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="],
|
||||||
|
|
||||||
"patch-package/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
|
"patch-package/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"path-scurry/lru-cache": ["lru-cache@11.5.1", "", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="],
|
"path-scurry/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="],
|
||||||
|
|
||||||
"pixelmatch/pngjs": ["pngjs@3.4.0", "", {}, "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="],
|
"pixelmatch/pngjs": ["pngjs@3.4.0", "", {}, "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="],
|
||||||
|
|
||||||
@@ -2183,17 +2193,17 @@
|
|||||||
|
|
||||||
"react-native/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
|
"react-native/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
|
||||||
|
|
||||||
"react-native/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
|
"react-native/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"react-native-drawer-layout/color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
|
"react-native-drawer-layout/color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
|
||||||
|
|
||||||
"react-native-reanimated/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
|
"react-native-reanimated/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"react-native-web/@react-native/normalize-colors": ["@react-native/normalize-colors@0.74.89", "", {}, "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg=="],
|
"react-native-web/@react-native/normalize-colors": ["@react-native/normalize-colors@0.74.89", "", {}, "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg=="],
|
||||||
|
|
||||||
"react-native-web/memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="],
|
"react-native-web/memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="],
|
||||||
|
|
||||||
"react-native-worklets/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="],
|
"react-native-worklets/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||||
|
|
||||||
"readable-web-to-node-stream/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
|
"readable-web-to-node-stream/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
|
||||||
|
|
||||||
@@ -2227,13 +2237,13 @@
|
|||||||
|
|
||||||
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||||
|
|
||||||
"wrap-ansi/string-width": ["string-width@8.2.1", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA=="],
|
"wrap-ansi/string-width": ["string-width@8.2.2", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg=="],
|
||||||
|
|
||||||
"wrap-ansi/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
"wrap-ansi/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||||
|
|
||||||
"xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
|
"xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
|
||||||
|
|
||||||
"zxing-wasm/type-fest": ["type-fest@5.7.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg=="],
|
"zxing-wasm/type-fest": ["type-fest@5.8.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA=="],
|
||||||
|
|
||||||
"@expo/cli/ora/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
|
"@expo/cli/ora/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
|
||||||
|
|
||||||
|
|||||||
52
components/PendingAccountSaveModal.tsx
Normal file
52
components/PendingAccountSaveModal.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
|
import type React from "react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
import { toast } from "sonner-native";
|
||||||
|
import { SaveAccountModal } from "@/components/SaveAccountModal";
|
||||||
|
import {
|
||||||
|
pendingAccountSaveAtom,
|
||||||
|
useJellyfin,
|
||||||
|
userAtom,
|
||||||
|
} from "@/providers/JellyfinProvider";
|
||||||
|
import { writeErrorLog } from "@/utils/log";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post-login save-account prompt. Login flows (password or Quick Connect)
|
||||||
|
* only flag the intent via pendingAccountSaveAtom; the protection picker
|
||||||
|
* shows here, AFTER the session is authorized — the login screen itself
|
||||||
|
* unmounts as soon as the user is set, so it can't host the modal.
|
||||||
|
*/
|
||||||
|
export const PendingAccountSaveModal: React.FC = () => {
|
||||||
|
const [pending, setPending] = useAtom(pendingAccountSaveAtom);
|
||||||
|
const user = useAtomValue(userAtom);
|
||||||
|
const { saveCurrentAccount } = useJellyfin();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
// A logout before answering drops the intent — it must not resurface on
|
||||||
|
// the next (possibly different) login.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!user && pending) setPending(null);
|
||||||
|
}, [user, pending, setPending]);
|
||||||
|
|
||||||
|
if (Platform.isTV) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SaveAccountModal
|
||||||
|
visible={!!pending && !!user}
|
||||||
|
username={user?.Name ?? ""}
|
||||||
|
onClose={() => setPending(null)}
|
||||||
|
onSave={(securityType, pinCode) => {
|
||||||
|
const serverName = pending?.serverName;
|
||||||
|
setPending(null);
|
||||||
|
saveCurrentAccount({ securityType, pinCode, serverName }).catch(
|
||||||
|
(error) => {
|
||||||
|
writeErrorLog(`Failed to save account: ${error?.message ?? error}`);
|
||||||
|
toast.error(t("save_account.not_saved"));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { BottomSheetScrollView } from "@gorhom/bottom-sheet";
|
import { BottomSheetScrollView } from "@gorhom/bottom-sheet";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { Platform, StyleSheet, TouchableOpacity, View } from "react-native";
|
import { Platform, StyleSheet, TouchableOpacity, View } from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
@@ -209,6 +210,7 @@ const PlatformDropdownComponent = ({
|
|||||||
expoUIConfig,
|
expoUIConfig,
|
||||||
bottomSheetConfig,
|
bottomSheetConfig,
|
||||||
}: PlatformDropdownProps) => {
|
}: PlatformDropdownProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { showModal, hideModal, isVisible } = useGlobalModal();
|
const { showModal, hideModal, isVisible } = useGlobalModal();
|
||||||
|
|
||||||
// Handle controlled open state for Android
|
// Handle controlled open state for Android
|
||||||
@@ -380,7 +382,7 @@ const PlatformDropdownComponent = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity onPress={handlePress} activeOpacity={0.7}>
|
<TouchableOpacity onPress={handlePress} activeOpacity={0.7}>
|
||||||
{trigger || <Text className='text-white'>Open Menu</Text>}
|
{trigger || <Text className='text-white'>{t("common.open_menu")}</Text>}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -502,8 +502,8 @@ export const PlayButton: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
disabled={!item}
|
disabled={!item}
|
||||||
accessibilityLabel='Play button'
|
accessibilityLabel={t("accessibility.play_button")}
|
||||||
accessibilityHint='Tap to play the media'
|
accessibilityHint={t("accessibility.play_hint")}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
className={"relative flex-1"}
|
className={"relative flex-1"}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Ionicons } from "@expo/vector-icons";
|
|||||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useCallback, useEffect } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { TouchableOpacity, View } from "react-native";
|
import { TouchableOpacity, View } from "react-native";
|
||||||
import Animated, {
|
import Animated, {
|
||||||
Easing,
|
Easing,
|
||||||
@@ -36,6 +37,7 @@ export const PlayButton: React.FC<Props> = ({
|
|||||||
colors,
|
colors,
|
||||||
...props
|
...props
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [globalColorAtom] = useAtom(itemThemeColorAtom);
|
const [globalColorAtom] = useAtom(itemThemeColorAtom);
|
||||||
|
|
||||||
// Use colors prop if provided, otherwise fallback to global atom
|
// Use colors prop if provided, otherwise fallback to global atom
|
||||||
@@ -168,8 +170,8 @@ export const PlayButton: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
accessibilityLabel='Play button'
|
accessibilityLabel={t("accessibility.play_button")}
|
||||||
accessibilityHint='Tap to play the media'
|
accessibilityHint={t("accessibility.play_hint")}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
className={"relative"}
|
className={"relative"}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api/session-api";
|
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api/session-api";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
FlatList,
|
FlatList,
|
||||||
Modal,
|
Modal,
|
||||||
@@ -31,6 +32,7 @@ export const PlayInRemoteSessionButton: React.FC<Props> = ({
|
|||||||
const [modalVisible, setModalVisible] = useState(false);
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
const api = useAtomValue(apiAtom);
|
const api = useAtomValue(apiAtom);
|
||||||
const { sessions, isLoading } = useAllSessions({} as useSessionsProps);
|
const { sessions, isLoading } = useAllSessions({} as useSessionsProps);
|
||||||
|
const { t } = useTranslation();
|
||||||
const handlePlayInSession = async (sessionId: string) => {
|
const handlePlayInSession = async (sessionId: string) => {
|
||||||
if (!api || !item.Id) return;
|
if (!api || !item.Id) return;
|
||||||
|
|
||||||
@@ -65,7 +67,9 @@ export const PlayInRemoteSessionButton: React.FC<Props> = ({
|
|||||||
<View style={styles.centeredView}>
|
<View style={styles.centeredView}>
|
||||||
<View style={styles.modalView}>
|
<View style={styles.modalView}>
|
||||||
<View style={styles.modalHeader}>
|
<View style={styles.modalHeader}>
|
||||||
<Text style={styles.modalTitle}>Select Session</Text>
|
<Text style={styles.modalTitle}>
|
||||||
|
{t("home.sessions.select_session")}
|
||||||
|
</Text>
|
||||||
<TouchableOpacity onPress={() => setModalVisible(false)}>
|
<TouchableOpacity onPress={() => setModalVisible(false)}>
|
||||||
<Ionicons name='close' size={24} color='white' />
|
<Ionicons name='close' size={24} color='white' />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
@@ -78,7 +82,7 @@ export const PlayInRemoteSessionButton: React.FC<Props> = ({
|
|||||||
</View>
|
</View>
|
||||||
) : !sessions || sessions.length === 0 ? (
|
) : !sessions || sessions.length === 0 ? (
|
||||||
<Text style={styles.noSessionsText}>
|
<Text style={styles.noSessionsText}>
|
||||||
No active sessions found
|
{t("home.sessions.no_active_sessions")}
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<FlatList
|
<FlatList
|
||||||
@@ -98,7 +102,7 @@ export const PlayInRemoteSessionButton: React.FC<Props> = ({
|
|||||||
</Text>
|
</Text>
|
||||||
{session.NowPlayingItem && (
|
{session.NowPlayingItem && (
|
||||||
<Text style={styles.nowPlaying} numberOfLines={1}>
|
<Text style={styles.nowPlaying} numberOfLines={1}>
|
||||||
Now playing:{" "}
|
{t("home.sessions.now_playing")}{" "}
|
||||||
{session.NowPlayingItem.SeriesName
|
{session.NowPlayingItem.SeriesName
|
||||||
? `${session.NowPlayingItem.SeriesName} :`
|
? `${session.NowPlayingItem.SeriesName} :`
|
||||||
: ""}
|
: ""}
|
||||||
|
|||||||
@@ -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,18 @@ 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. hitSlop
|
||||||
|
// keeps the touch area at ~44dp now that the padding is gone.
|
||||||
|
style={{ marginRight: 16 }}
|
||||||
{...pressableProps}
|
{...pressableProps}
|
||||||
|
hitSlop={8}
|
||||||
>
|
>
|
||||||
<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>
|
||||||
|
|||||||
40
components/common/SettingSwitch.tsx
Normal file
40
components/common/SettingSwitch.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import type React from "react";
|
||||||
|
import { Platform, Switch, type SwitchProps, View } from "react-native";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Settings toggle. Android's native Switch lays out ~40px tall / ~56px wide and
|
||||||
|
* inflates list rows (iOS renders it ~31px). A plain `transform: scale` is
|
||||||
|
* visual-only and does NOT shrink the layout box, so we pin the Switch inside a
|
||||||
|
* FIXED-SIZE box (overflow hidden) and center it:
|
||||||
|
* - the fixed height caps the row height (compact, uniform rows),
|
||||||
|
* - the fixed width + centering keep the switch in the exact same spot in the
|
||||||
|
* on/off states (a non-fixed wrapper let its width fluctuate between states,
|
||||||
|
* which shifted the switch sideways on toggle).
|
||||||
|
* iOS renders the switch untouched.
|
||||||
|
*
|
||||||
|
* Tunables: BOX_H drives the row height; SCALE shrinks the visual to fit the
|
||||||
|
* box; keep BOX_W >= scaled visual width to avoid clipping the switch sideways.
|
||||||
|
*/
|
||||||
|
const BOX_W = 40;
|
||||||
|
const BOX_H = 30;
|
||||||
|
const SCALE = 0.9;
|
||||||
|
|
||||||
|
export const SettingSwitch: React.FC<SwitchProps> = (props) => {
|
||||||
|
if (Platform.OS !== "android") return <Switch {...props} />;
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
width: BOX_W,
|
||||||
|
height: BOX_H,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
{...props}
|
||||||
|
style={[props.style, { transform: [{ scale: SCALE }] }]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import { t } from "i18next";
|
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
@@ -35,6 +35,7 @@ interface DownloadCardProps extends TouchableOpacityProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
|
export const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { cancelDownload } = useDownload();
|
const { cancelDownload } = useDownload();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const queryClient = useNetworkAwareQueryClient();
|
const queryClient = useNetworkAwareQueryClient();
|
||||||
@@ -173,7 +174,9 @@ export const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
|
|||||||
|
|
||||||
{isTranscoding && (
|
{isTranscoding && (
|
||||||
<View className='bg-purple-600/20 px-2 py-0.5 rounded-md mt-1 self-start'>
|
<View className='bg-purple-600/20 px-2 py-0.5 rounded-md mt-1 self-start'>
|
||||||
<Text className='text-xs text-purple-400'>Transcoding</Text>
|
<Text className='text-xs text-purple-400'>
|
||||||
|
{t("home.downloads.transcoding")}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,11 @@ import { useRefreshLibraryOnFocus } from "@/hooks/useRefreshLibraryOnFocus";
|
|||||||
import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache";
|
import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache";
|
||||||
import { useDownload } from "@/providers/DownloadProvider";
|
import { useDownload } from "@/providers/DownloadProvider";
|
||||||
import { useIntroSheet } from "@/providers/IntroSheetProvider";
|
import { useIntroSheet } from "@/providers/IntroSheetProvider";
|
||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
import {
|
||||||
|
apiAtom,
|
||||||
|
pendingAccountSaveAtom,
|
||||||
|
userAtom,
|
||||||
|
} from "@/providers/JellyfinProvider";
|
||||||
import { SortByOption, SortOrderOption } from "@/utils/atoms/filters";
|
import { SortByOption, SortOrderOption } from "@/utils/atoms/filters";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
import { eventBus } from "@/utils/eventBus";
|
import { eventBus } from "@/utils/eventBus";
|
||||||
@@ -89,6 +93,9 @@ const HomeMobile = () => {
|
|||||||
const invalidateCache = useInvalidatePlaybackProgressCache();
|
const invalidateCache = useInvalidatePlaybackProgressCache();
|
||||||
const [loadedSections, setLoadedSections] = useState<Set<string>>(new Set());
|
const [loadedSections, setLoadedSections] = useState<Set<string>>(new Set());
|
||||||
const { showIntro } = useIntroSheet();
|
const { showIntro } = useIntroSheet();
|
||||||
|
// Gate the intro so it can't steal presentation from the post-login
|
||||||
|
// save-account sheet (both are BottomSheetModals): wait until no save is pending.
|
||||||
|
const pendingAccountSave = useAtomValue(pendingAccountSaveAtom);
|
||||||
|
|
||||||
// Fallback refresh for newly added content when returning to the home screen
|
// Fallback refresh for newly added content when returning to the home screen
|
||||||
// (primary path is the LibraryChanged WebSocket event).
|
// (primary path is the LibraryChanged WebSocket event).
|
||||||
@@ -97,7 +104,9 @@ const HomeMobile = () => {
|
|||||||
// Show intro modal on first launch
|
// Show intro modal on first launch
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hasShownIntro = storage.getBoolean("hasShownIntro");
|
const hasShownIntro = storage.getBoolean("hasShownIntro");
|
||||||
if (!hasShownIntro) {
|
// Defer while the save-account sheet is up; this effect re-runs and schedules
|
||||||
|
// the intro once the sheet is dismissed (pendingAccountSaveAtom cleared).
|
||||||
|
if (!hasShownIntro && !pendingAccountSave) {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
showIntro();
|
showIntro();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
@@ -106,7 +115,7 @@ const HomeMobile = () => {
|
|||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [showIntro]);
|
}, [showIntro, pendingAccountSave]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isConnected && !prevIsConnected.current) {
|
if (isConnected && !prevIsConnected.current) {
|
||||||
@@ -221,22 +230,33 @@ const HomeMobile = () => {
|
|||||||
queryKey,
|
queryKey,
|
||||||
queryFn: async ({ pageParam = 0 }) => {
|
queryFn: async ({ pageParam = 0 }) => {
|
||||||
if (!api) return [];
|
if (!api) return [];
|
||||||
// getLatestMedia doesn't support startIndex, so we fetch all and slice client-side
|
// Use getItems (not getLatestMedia) so we get item-level results
|
||||||
const allData =
|
// filtered by type from a specific library. getLatestMedia is
|
||||||
(
|
// episode-oriented and groups results, which drops Series when
|
||||||
await getUserLibraryApi(api).getLatestMedia({
|
// combined with a parentId + includeItemTypes filter.
|
||||||
userId: user?.Id,
|
//
|
||||||
limit: 10,
|
// The specific reason for this is jellyfin 12.0 returns episodes, seasons, or shows,
|
||||||
fields: ["PrimaryImageAspectRatio"],
|
// but we only handle shows in our recently added in [shows] section. So we need to filter by type at the item level.
|
||||||
imageTypeLimit: 1,
|
//
|
||||||
enableImageTypes: ["Primary", "Backdrop", "Thumb"],
|
// For Series we sort by DateLastContentAdded so shows bubble up when
|
||||||
includeItemTypes,
|
// a new episode is added (series cards for new episodes, matching how
|
||||||
parentId,
|
// Jellyfin's "Latest" row worked pre-12.0). Movies use DateCreated.
|
||||||
})
|
const response = await getItemsApi(api).getItems({
|
||||||
).data || [];
|
userId: user?.Id,
|
||||||
|
parentId,
|
||||||
// Simulate pagination by slicing
|
includeItemTypes,
|
||||||
return allData.slice(pageParam, pageParam + pageSize);
|
recursive: true,
|
||||||
|
sortBy: includeItemTypes.includes("Series")
|
||||||
|
? ["DateLastContentAdded"]
|
||||||
|
: ["DateCreated"],
|
||||||
|
sortOrder: ["Descending"],
|
||||||
|
startIndex: pageParam,
|
||||||
|
limit: pageSize,
|
||||||
|
fields: ["PrimaryImageAspectRatio"],
|
||||||
|
imageTypeLimit: 1,
|
||||||
|
enableImageTypes: ["Primary", "Backdrop", "Thumb"],
|
||||||
|
});
|
||||||
|
return response.data.Items || [];
|
||||||
},
|
},
|
||||||
type: "InfiniteScrollingCollectionList",
|
type: "InfiniteScrollingCollectionList",
|
||||||
pageSize,
|
pageSize,
|
||||||
@@ -250,9 +270,7 @@ const HomeMobile = () => {
|
|||||||
|
|
||||||
const latestMediaViews = collections.map((c) => {
|
const latestMediaViews = collections.map((c) => {
|
||||||
const includeItemTypes: BaseItemKind[] =
|
const includeItemTypes: BaseItemKind[] =
|
||||||
c.CollectionType === "tvshows" || c.CollectionType === "movies"
|
c.CollectionType === "tvshows" ? ["Series"] : ["Movie"];
|
||||||
? []
|
|
||||||
: ["Movie"];
|
|
||||||
const title = t("home.recently_added_in", { libraryName: c.Name });
|
const title = t("home.recently_added_in", { libraryName: c.Name });
|
||||||
const queryKey: string[] = [
|
const queryKey: string[] = [
|
||||||
"home",
|
"home",
|
||||||
|
|||||||
@@ -23,9 +23,11 @@ export const ListGroup: React.FC<PropsWithChildren<Props>> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View {...props}>
|
<View {...props}>
|
||||||
<Text className='ml-4 mb-1 uppercase text-[#8E8D91] text-xs'>
|
{title ? (
|
||||||
{title}
|
<Text className='ml-4 mb-1 uppercase text-[#8E8D91] text-xs'>
|
||||||
</Text>
|
{title}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
<View
|
<View
|
||||||
style={[]}
|
style={[]}
|
||||||
className='flex flex-col rounded-xl overflow-hidden pl-0 bg-neutral-900'
|
className='flex flex-col rounded-xl overflow-hidden pl-0 bg-neutral-900'
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import type { PropsWithChildren, ReactNode } from "react";
|
import type { PropsWithChildren, ReactNode } from "react";
|
||||||
import { TouchableOpacity, View, type ViewProps } from "react-native";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Platform, TouchableOpacity, View, type ViewProps } from "react-native";
|
||||||
import { Text } from "../common/Text";
|
import { Text } from "../common/Text";
|
||||||
|
|
||||||
interface Props extends ViewProps {
|
interface Props extends ViewProps {
|
||||||
@@ -32,14 +33,22 @@ export const ListItem: React.FC<PropsWithChildren<Props>> = ({
|
|||||||
disabledByAdmin = false,
|
disabledByAdmin = false,
|
||||||
...viewProps
|
...viewProps
|
||||||
}) => {
|
}) => {
|
||||||
const effectiveSubtitle = disabledByAdmin ? "Disabled by admin" : subtitle;
|
const { t } = useTranslation();
|
||||||
|
const effectiveSubtitle = disabledByAdmin
|
||||||
|
? t("home.settings.disabled_by_admin")
|
||||||
|
: subtitle;
|
||||||
const isDisabled = disabled || disabledByAdmin;
|
const isDisabled = disabled || disabledByAdmin;
|
||||||
|
// Keep the row floor uniform; Android trims padding slightly (its native
|
||||||
|
// controls sit taller). Switch height is capped via SettingSwitch so toggle
|
||||||
|
// rows match non-toggle rows.
|
||||||
|
const rowSizing =
|
||||||
|
Platform.OS === "android" ? "min-h-[42px] py-1.5" : "min-h-[42px] py-2";
|
||||||
if (onPress)
|
if (onPress)
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
className={`flex flex-row items-center justify-between bg-neutral-900 min-h-[42px] py-2 pr-4 pl-4 ${isDisabled ? "opacity-50" : ""}`}
|
className={`flex flex-row items-center justify-between bg-neutral-900 ${rowSizing} pr-4 pl-4 ${isDisabled ? "opacity-50" : ""}`}
|
||||||
{...(viewProps as any)}
|
{...(viewProps as any)}
|
||||||
>
|
>
|
||||||
<ListItemContent
|
<ListItemContent
|
||||||
@@ -58,7 +67,7 @@ export const ListItem: React.FC<PropsWithChildren<Props>> = ({
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
className={`flex flex-row items-center justify-between bg-neutral-900 min-h-[42px] py-2 pr-4 pl-4 ${isDisabled ? "opacity-50" : ""}`}
|
className={`flex flex-row items-center justify-between bg-neutral-900 ${rowSizing} pr-4 pl-4 ${isDisabled ? "opacity-50" : ""}`}
|
||||||
{...viewProps}
|
{...viewProps}
|
||||||
>
|
>
|
||||||
<ListItemContent
|
<ListItemContent
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { Animated, Pressable, StyleSheet, View } from "react-native";
|
import { Animated, Pressable, StyleSheet, View } from "react-native";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
||||||
@@ -22,6 +23,7 @@ export const TVGuideProgramCell: React.FC<TVGuideProgramCellProps> = ({
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
refSetter,
|
refSetter,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const typography = useScaledTVTypography();
|
const typography = useScaledTVTypography();
|
||||||
const { focused, handleFocus, handleBlur } = useTVFocusAnimation({
|
const { focused, handleFocus, handleBlur } = useTVFocusAnimation({
|
||||||
scaleAmount: 1,
|
scaleAmount: 1,
|
||||||
@@ -68,7 +70,7 @@ export const TVGuideProgramCell: React.FC<TVGuideProgramCellProps> = ({
|
|||||||
<Text
|
<Text
|
||||||
style={[styles.liveBadgeText, { fontSize: typography.callout }]}
|
style={[styles.liveBadgeText, { fontSize: typography.callout }]}
|
||||||
>
|
>
|
||||||
LIVE
|
{t("player.live")}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ export const TVLiveTVPage: React.FC = () => {
|
|||||||
marginBottom: 24,
|
marginBottom: 24,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Live TV
|
{t("live_tv.title")}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{/* Tab Bar */}
|
{/* Tab Bar */}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { PublicSystemInfo } from "@jellyfin/sdk/lib/generated-client";
|
|||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import { useLocalSearchParams, useNavigation } from "expo-router";
|
import { useLocalSearchParams, useNavigation } from "expo-router";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue, useSetAtom } from "jotai";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
@@ -20,14 +20,16 @@ import { Button } from "@/components/Button";
|
|||||||
import { Input } from "@/components/common/Input";
|
import { Input } from "@/components/common/Input";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import JellyfinServerDiscovery from "@/components/JellyfinServerDiscovery";
|
import JellyfinServerDiscovery from "@/components/JellyfinServerDiscovery";
|
||||||
|
import { QuickConnectCodeModal } from "@/components/login/QuickConnectCodeModal";
|
||||||
import { PreviousServersList } from "@/components/PreviousServersList";
|
import { PreviousServersList } from "@/components/PreviousServersList";
|
||||||
import { SaveAccountModal } from "@/components/SaveAccountModal";
|
|
||||||
import { Colors } from "@/constants/Colors";
|
import { Colors } from "@/constants/Colors";
|
||||||
import { apiAtom, useJellyfin } from "@/providers/JellyfinProvider";
|
import {
|
||||||
import type {
|
apiAtom,
|
||||||
AccountSecurityType,
|
pendingAccountSaveAtom,
|
||||||
SavedServer,
|
useJellyfin,
|
||||||
} from "@/utils/secureCredentials";
|
userAtom,
|
||||||
|
} from "@/providers/JellyfinProvider";
|
||||||
|
import type { SavedServer } from "@/utils/secureCredentials";
|
||||||
|
|
||||||
const CredentialsSchema = z.object({
|
const CredentialsSchema = z.object({
|
||||||
username: z.string().min(1, t("login.username_required")),
|
username: z.string().min(1, t("login.username_required")),
|
||||||
@@ -35,6 +37,7 @@ const CredentialsSchema = z.object({
|
|||||||
|
|
||||||
export const Login: React.FC = () => {
|
export const Login: React.FC = () => {
|
||||||
const api = useAtomValue(apiAtom);
|
const api = useAtomValue(apiAtom);
|
||||||
|
const user = useAtomValue(userAtom);
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const params = useLocalSearchParams();
|
const params = useLocalSearchParams();
|
||||||
const {
|
const {
|
||||||
@@ -42,9 +45,11 @@ export const Login: React.FC = () => {
|
|||||||
login,
|
login,
|
||||||
removeServer,
|
removeServer,
|
||||||
initiateQuickConnect,
|
initiateQuickConnect,
|
||||||
|
stopQuickConnectPolling,
|
||||||
loginWithSavedCredential,
|
loginWithSavedCredential,
|
||||||
loginWithPassword,
|
loginWithPassword,
|
||||||
} = useJellyfin();
|
} = useJellyfin();
|
||||||
|
const setPendingAccountSave = useSetAtom(pendingAccountSaveAtom);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
apiUrl: _apiUrl,
|
apiUrl: _apiUrl,
|
||||||
@@ -64,13 +69,50 @@ export const Login: React.FC = () => {
|
|||||||
password: _password || "",
|
password: _password || "",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save account state
|
// Quick Connect code shown in the in-app sheet while polling for authorization
|
||||||
|
const [quickConnectCode, setQuickConnectCode] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Close the code sheet as soon as the session is authorized — the native
|
||||||
|
// Alert used before had no programmatic dismiss and stayed open after login.
|
||||||
|
useEffect(() => {
|
||||||
|
if (user) setQuickConnectCode(null);
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
// Stop Quick Connect polling when leaving the login page (parity with TVLogin)
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
stopQuickConnectPolling();
|
||||||
|
};
|
||||||
|
}, [stopQuickConnectPolling]);
|
||||||
|
|
||||||
|
// Going back to server selection keeps this component mounted (same screen,
|
||||||
|
// different state), so the unmount cleanup above doesn't run. Without this a
|
||||||
|
// code authorized after leaving would silently log the user in later.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!api?.basePath) {
|
||||||
|
stopQuickConnectPolling();
|
||||||
|
setQuickConnectCode(null);
|
||||||
|
}
|
||||||
|
}, [api?.basePath, stopQuickConnectPolling]);
|
||||||
|
|
||||||
|
// Save account state — only the intent lives here; the protection picker is
|
||||||
|
// the global PendingAccountSaveModal, shown after the login succeeds.
|
||||||
const [saveAccount, setSaveAccount] = useState(false);
|
const [saveAccount, setSaveAccount] = useState(false);
|
||||||
const [showSaveModal, setShowSaveModal] = useState(false);
|
|
||||||
const [pendingLogin, setPendingLogin] = useState<{
|
// Tracks an in-flight Quick Connect attempt (code issued, provider polling).
|
||||||
username: string;
|
const [quickConnectActive, setQuickConnectActive] = useState(false);
|
||||||
password: string;
|
|
||||||
} | null>(null);
|
// A Quick Connect login with "save account" on flags the post-login save:
|
||||||
|
// the protection picker shows globally once the session exists (this screen
|
||||||
|
// unmounts on login, so it can't host the modal).
|
||||||
|
useEffect(() => {
|
||||||
|
if (user) {
|
||||||
|
if (quickConnectActive && saveAccount) {
|
||||||
|
setPendingAccountSave({ serverName });
|
||||||
|
}
|
||||||
|
setQuickConnectActive(false);
|
||||||
|
}
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
// Handle URL params for server connection
|
// Handle URL params for server connection
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -117,29 +159,22 @@ export const Login: React.FC = () => {
|
|||||||
const result = CredentialsSchema.safeParse(credentials);
|
const result = CredentialsSchema.safeParse(credentials);
|
||||||
if (!result.success) return;
|
if (!result.success) return;
|
||||||
|
|
||||||
if (saveAccount) {
|
const ok = await performLogin(credentials.username, credentials.password);
|
||||||
setPendingLogin({
|
// The protection picker shows AFTER a successful login (global modal) —
|
||||||
username: credentials.username,
|
// never for a failed one.
|
||||||
password: credentials.password,
|
if (ok && saveAccount) {
|
||||||
});
|
setPendingAccountSave({ serverName });
|
||||||
setShowSaveModal(true);
|
|
||||||
} else {
|
|
||||||
await performLogin(credentials.username, credentials.password);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const performLogin = async (
|
const performLogin = async (
|
||||||
username: string,
|
username: string,
|
||||||
password: string,
|
password: string,
|
||||||
options?: {
|
): Promise<boolean> => {
|
||||||
saveAccount?: boolean;
|
|
||||||
securityType?: AccountSecurityType;
|
|
||||||
pinCode?: string;
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
await login(username, password, serverName, options);
|
await login(username, password, serverName);
|
||||||
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
Alert.alert(t("login.connection_failed"), error.message);
|
Alert.alert(t("login.connection_failed"), error.message);
|
||||||
@@ -149,23 +184,9 @@ export const Login: React.FC = () => {
|
|||||||
t("login.an_unexpected_error_occurred"),
|
t("login.an_unexpected_error_occurred"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setPendingLogin(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSaveAccountConfirm = async (
|
|
||||||
securityType: AccountSecurityType,
|
|
||||||
pinCode?: string,
|
|
||||||
) => {
|
|
||||||
setShowSaveModal(false);
|
|
||||||
if (pendingLogin) {
|
|
||||||
await performLogin(pendingLogin.username, pendingLogin.password, {
|
|
||||||
saveAccount: true,
|
|
||||||
securityType,
|
|
||||||
pinCode,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -259,15 +280,8 @@ export const Login: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
const code = await initiateQuickConnect();
|
const code = await initiateQuickConnect();
|
||||||
if (code) {
|
if (code) {
|
||||||
Alert.alert(
|
setQuickConnectActive(true);
|
||||||
t("login.quick_connect"),
|
setQuickConnectCode(code);
|
||||||
t("login.enter_code_to_login", { code: code }),
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: t("login.got_it"),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
@@ -402,7 +416,7 @@ export const Login: React.FC = () => {
|
|||||||
{t("server.enter_url_to_jellyfin_server")}
|
{t("server.enter_url_to_jellyfin_server")}
|
||||||
</Text>
|
</Text>
|
||||||
<Input
|
<Input
|
||||||
aria-label='Server URL'
|
aria-label={t("server.server_url")}
|
||||||
placeholder={t("server.server_url_placeholder")}
|
placeholder={t("server.server_url_placeholder")}
|
||||||
onChangeText={setServerURL}
|
onChangeText={setServerURL}
|
||||||
value={serverURL}
|
value={serverURL}
|
||||||
@@ -444,14 +458,11 @@ export const Login: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
|
|
||||||
<SaveAccountModal
|
{/* Dismissing only hides the code — polling continues so the login still
|
||||||
visible={showSaveModal}
|
completes if the code is authorized from another device afterwards. */}
|
||||||
onClose={() => {
|
<QuickConnectCodeModal
|
||||||
setShowSaveModal(false);
|
code={quickConnectCode}
|
||||||
setPendingLogin(null);
|
onClose={() => setQuickConnectCode(null)}
|
||||||
}}
|
|
||||||
onSave={handleSaveAccountConfirm}
|
|
||||||
username={pendingLogin?.username || credentials.username}
|
|
||||||
/>
|
/>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
|
|||||||
137
components/login/QuickConnectCodeModal.tsx
Normal file
137
components/login/QuickConnectCodeModal.tsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
|
import {
|
||||||
|
BottomSheetBackdrop,
|
||||||
|
type BottomSheetBackdropProps,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetView,
|
||||||
|
} from "@gorhom/bottom-sheet";
|
||||||
|
import { requireOptionalNativeModule } from "expo-modules-core";
|
||||||
|
import type React from "react";
|
||||||
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { TouchableOpacity, View } from "react-native";
|
||||||
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
import { toast } from "sonner-native";
|
||||||
|
import { Button } from "../Button";
|
||||||
|
import { Text } from "../common/Text";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/** The Quick Connect code to display, or null when hidden. */
|
||||||
|
code: string | null;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the Quick Connect code while the app polls for authorization.
|
||||||
|
* In-app sheet instead of a native Alert so it can dismiss itself once the
|
||||||
|
* session is authorized — a native alert has no programmatic dismiss and
|
||||||
|
* lingers over the app after login completes.
|
||||||
|
*/
|
||||||
|
export const QuickConnectCodeModal: React.FC<Props> = ({ code, onClose }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
|
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
||||||
|
const snapPoints = useMemo(() => ["50%"], []);
|
||||||
|
const isPresentedRef = useRef(false);
|
||||||
|
|
||||||
|
// Keep the last code around so the dismiss animation doesn't flash empty
|
||||||
|
// when the parent clears the code to close the sheet.
|
||||||
|
const lastCodeRef = useRef<string | null>(null);
|
||||||
|
if (code) lastCodeRef.current = code;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (code) {
|
||||||
|
bottomSheetModalRef.current?.present();
|
||||||
|
} else if (isPresentedRef.current) {
|
||||||
|
bottomSheetModalRef.current?.dismiss();
|
||||||
|
isPresentedRef.current = false;
|
||||||
|
}
|
||||||
|
}, [code]);
|
||||||
|
|
||||||
|
const handleSheetChanges = useCallback(
|
||||||
|
(index: number) => {
|
||||||
|
if (index >= 0) {
|
||||||
|
isPresentedRef.current = true;
|
||||||
|
} else if (index === -1 && isPresentedRef.current) {
|
||||||
|
isPresentedRef.current = false;
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onClose],
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderBackdrop = useCallback(
|
||||||
|
(props: BottomSheetBackdropProps) => (
|
||||||
|
<BottomSheetBackdrop
|
||||||
|
{...props}
|
||||||
|
disappearsOnIndex={-1}
|
||||||
|
appearsOnIndex={0}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const copyCode = useCallback(async () => {
|
||||||
|
const value = code ?? lastCodeRef.current;
|
||||||
|
if (!value) return;
|
||||||
|
// Builds that don't ship the expo-clipboard native module yet: probe with
|
||||||
|
// requireOptionalNativeModule (returns null instead of throwing/logging)
|
||||||
|
// and skip — importing the JS wrapper there would error out.
|
||||||
|
if (!requireOptionalNativeModule("ExpoClipboard")) return;
|
||||||
|
const Clipboard = await import("expo-clipboard");
|
||||||
|
await Clipboard.setStringAsync(value);
|
||||||
|
toast.success(t("login.code_copied"));
|
||||||
|
}, [code, t]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BottomSheetModal
|
||||||
|
ref={bottomSheetModalRef}
|
||||||
|
snapPoints={snapPoints}
|
||||||
|
onChange={handleSheetChanges}
|
||||||
|
handleIndicatorStyle={{ backgroundColor: "white" }}
|
||||||
|
backgroundStyle={{ backgroundColor: "#171717" }}
|
||||||
|
backdropComponent={renderBackdrop}
|
||||||
|
>
|
||||||
|
<BottomSheetView
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
paddingLeft: Math.max(16, insets.left),
|
||||||
|
paddingRight: Math.max(16, insets.right),
|
||||||
|
paddingBottom: Math.max(16, insets.bottom),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View className='flex-1'>
|
||||||
|
<Text className='font-bold text-2xl text-neutral-100'>
|
||||||
|
{t("login.quick_connect")}
|
||||||
|
</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
className='mt-6 p-6 border border-neutral-800 rounded-xl bg-neutral-900 flex flex-row items-center justify-center'
|
||||||
|
onPress={copyCode}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
className='text-center font-bold text-5xl text-neutral-100'
|
||||||
|
style={{ letterSpacing: 10 }}
|
||||||
|
>
|
||||||
|
{code ?? lastCodeRef.current}
|
||||||
|
</Text>
|
||||||
|
<Ionicons
|
||||||
|
name='copy-outline'
|
||||||
|
size={22}
|
||||||
|
color='white'
|
||||||
|
style={{ opacity: 0.4, marginLeft: 16 }}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<Text className='mt-2 text-neutral-500 text-center text-xs'>
|
||||||
|
{t("login.tap_code_to_copy")}
|
||||||
|
</Text>
|
||||||
|
<Text className='mt-3 mb-5 text-neutral-400 text-center px-4'>
|
||||||
|
{t("login.quick_connect_instructions")}
|
||||||
|
</Text>
|
||||||
|
<Button className='mt-auto' color='purple' onPress={onClose}>
|
||||||
|
{t("login.got_it")}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</BottomSheetView>
|
||||||
|
</BottomSheetModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { Animated, Pressable, View } from "react-native";
|
import { Animated, Pressable, View } from "react-native";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
import { useTVFocusAnimation } from "@/components/tv/hooks/useTVFocusAnimation";
|
||||||
@@ -88,6 +89,8 @@ export const TVSearchTabBadges: React.FC<TVSearchTabBadgesProps> = ({
|
|||||||
showDiscover,
|
showDiscover,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (!showDiscover) {
|
if (!showDiscover) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -101,13 +104,13 @@ export const TVSearchTabBadges: React.FC<TVSearchTabBadgesProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TVSearchTabBadge
|
<TVSearchTabBadge
|
||||||
label='Library'
|
label={t("search.library")}
|
||||||
isSelected={searchType === "Library"}
|
isSelected={searchType === "Library"}
|
||||||
onPress={() => setSearchType("Library")}
|
onPress={() => setSearchType("Library")}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
<TVSearchTabBadge
|
<TVSearchTabBadge
|
||||||
label='Discover'
|
label={t("search.discover")}
|
||||||
isSelected={searchType === "Discover"}
|
isSelected={searchType === "Discover"}
|
||||||
onPress={() => setSearchType("Discover")}
|
onPress={() => setSearchType("Discover")}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Linking, Switch } from "react-native";
|
import { Linking } from "react-native";
|
||||||
|
import { SettingSwitch } from "@/components/common/SettingSwitch";
|
||||||
import DisabledSetting from "@/components/settings/DisabledSetting";
|
import DisabledSetting from "@/components/settings/DisabledSetting";
|
||||||
import useRouter from "@/hooks/useAppRouter";
|
import useRouter from "@/hooks/useAppRouter";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
@@ -35,7 +36,7 @@ export const AppearanceSettings: React.FC = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.showCustomMenuLinks}
|
value={settings.showCustomMenuLinks}
|
||||||
disabled={pluginSettings?.showCustomMenuLinks?.locked}
|
disabled={pluginSettings?.showCustomMenuLinks?.locked}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
@@ -49,7 +50,7 @@ export const AppearanceSettings: React.FC = () => {
|
|||||||
"home.settings.appearance.merge_next_up_continue_watching_hint",
|
"home.settings.appearance.merge_next_up_continue_watching_hint",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.mergeNextUpAndContinueWatching}
|
value={settings.mergeNextUpAndContinueWatching}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
updateSettings({ mergeNextUpAndContinueWatching: value })
|
updateSettings({ mergeNextUpAndContinueWatching: value })
|
||||||
@@ -62,13 +63,26 @@ export const AppearanceSettings: React.FC = () => {
|
|||||||
"home.settings.appearance.use_episode_images_next_up_hint",
|
"home.settings.appearance.use_episode_images_next_up_hint",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.useEpisodeImagesForNextUp}
|
value={settings.useEpisodeImagesForNextUp}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
updateSettings({ useEpisodeImagesForNextUp: value })
|
updateSettings({ useEpisodeImagesForNextUp: value })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
<ListItem
|
||||||
|
title={t("home.settings.appearance.hide_remote_session_button")}
|
||||||
|
subtitle={t(
|
||||||
|
"home.settings.appearance.hide_remote_session_button_hint",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<SettingSwitch
|
||||||
|
value={settings.hideRemoteSessionButton}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
updateSettings({ hideRemoteSessionButton: value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
<ListItem
|
<ListItem
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
router.push("/settings/appearance/hide-libraries/page")
|
router.push("/settings/appearance/hide-libraries/page")
|
||||||
@@ -77,19 +91,6 @@ export const AppearanceSettings: React.FC = () => {
|
|||||||
subtitle={t("home.settings.other.select_libraries_you_want_to_hide")}
|
subtitle={t("home.settings.other.select_libraries_you_want_to_hide")}
|
||||||
showArrow
|
showArrow
|
||||||
/>
|
/>
|
||||||
<ListItem
|
|
||||||
title={t("home.settings.appearance.hide_remote_session_button")}
|
|
||||||
subtitle={t(
|
|
||||||
"home.settings.appearance.hide_remote_session_button_hint",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Switch
|
|
||||||
value={settings.hideRemoteSessionButton}
|
|
||||||
onValueChange={(value) =>
|
|
||||||
updateSettings({ hideRemoteSessionButton: value })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
</DisabledSetting>
|
</DisabledSetting>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Ionicons } from "@expo/vector-icons";
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Platform, View, type ViewProps } from "react-native";
|
import { Platform, View, type ViewProps } from "react-native";
|
||||||
import { Switch } from "react-native-gesture-handler";
|
import { SettingSwitch } from "@/components/common/SettingSwitch";
|
||||||
import { AudioTranscodeMode, useSettings } from "@/utils/atoms/settings";
|
import { AudioTranscodeMode, useSettings } from "@/utils/atoms/settings";
|
||||||
import { Text } from "../common/Text";
|
import { Text } from "../common/Text";
|
||||||
import { ListGroup } from "../list/ListGroup";
|
import { ListGroup } from "../list/ListGroup";
|
||||||
@@ -135,7 +135,7 @@ export const AudioToggles: React.FC<Props> = ({ ...props }) => {
|
|||||||
title={t("home.settings.audio.set_audio_track")}
|
title={t("home.settings.audio.set_audio_track")}
|
||||||
disabled={pluginSettings?.rememberAudioSelections?.locked}
|
disabled={pluginSettings?.rememberAudioSelections?.locked}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.rememberAudioSelections}
|
value={settings.rememberAudioSelections}
|
||||||
disabled={pluginSettings?.rememberAudioSelections?.locked}
|
disabled={pluginSettings?.rememberAudioSelections?.locked}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Switch, View } from "react-native";
|
import { View } from "react-native";
|
||||||
|
import { SettingSwitch } from "@/components/common/SettingSwitch";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
import { ListGroup } from "../list/ListGroup";
|
import { ListGroup } from "../list/ListGroup";
|
||||||
import { ListItem } from "../list/ListItem";
|
import { ListItem } from "../list/ListItem";
|
||||||
@@ -9,7 +10,7 @@ export const ChromecastSettings: React.FC = ({ ...props }) => {
|
|||||||
<View {...props}>
|
<View {...props}>
|
||||||
<ListGroup title={"Chromecast"}>
|
<ListGroup title={"Chromecast"}>
|
||||||
<ListItem title={"Enable H265 for Chromecast"}>
|
<ListItem title={"Enable H265 for Chromecast"}>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.enableH265ForChromecast}
|
value={settings.enableH265ForChromecast}
|
||||||
onValueChange={(enableH265ForChromecast) =>
|
onValueChange={(enableH265ForChromecast) =>
|
||||||
updateSettings({ enableH265ForChromecast })
|
updateSettings({ enableH265ForChromecast })
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type React from "react";
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import type { ViewProps } from "react-native";
|
import type { ViewProps } from "react-native";
|
||||||
import { Switch } from "react-native";
|
import { SettingSwitch } from "@/components/common/SettingSwitch";
|
||||||
import DisabledSetting from "@/components/settings/DisabledSetting";
|
import DisabledSetting from "@/components/settings/DisabledSetting";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
import { ListGroup } from "../list/ListGroup";
|
import { ListGroup } from "../list/ListGroup";
|
||||||
@@ -39,7 +39,7 @@ export const GestureControls: React.FC<Props> = ({ ...props }) => {
|
|||||||
)}
|
)}
|
||||||
disabled={pluginSettings?.enableHorizontalSwipeSkip?.locked}
|
disabled={pluginSettings?.enableHorizontalSwipeSkip?.locked}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.enableHorizontalSwipeSkip}
|
value={settings.enableHorizontalSwipeSkip}
|
||||||
disabled={pluginSettings?.enableHorizontalSwipeSkip?.locked}
|
disabled={pluginSettings?.enableHorizontalSwipeSkip?.locked}
|
||||||
onValueChange={(enableHorizontalSwipeSkip) =>
|
onValueChange={(enableHorizontalSwipeSkip) =>
|
||||||
@@ -55,7 +55,7 @@ export const GestureControls: React.FC<Props> = ({ ...props }) => {
|
|||||||
)}
|
)}
|
||||||
disabled={pluginSettings?.enableLeftSideBrightnessSwipe?.locked}
|
disabled={pluginSettings?.enableLeftSideBrightnessSwipe?.locked}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.enableLeftSideBrightnessSwipe}
|
value={settings.enableLeftSideBrightnessSwipe}
|
||||||
disabled={pluginSettings?.enableLeftSideBrightnessSwipe?.locked}
|
disabled={pluginSettings?.enableLeftSideBrightnessSwipe?.locked}
|
||||||
onValueChange={(enableLeftSideBrightnessSwipe) =>
|
onValueChange={(enableLeftSideBrightnessSwipe) =>
|
||||||
@@ -71,7 +71,7 @@ export const GestureControls: React.FC<Props> = ({ ...props }) => {
|
|||||||
)}
|
)}
|
||||||
disabled={pluginSettings?.enableRightSideVolumeSwipe?.locked}
|
disabled={pluginSettings?.enableRightSideVolumeSwipe?.locked}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.enableRightSideVolumeSwipe}
|
value={settings.enableRightSideVolumeSwipe}
|
||||||
disabled={pluginSettings?.enableRightSideVolumeSwipe?.locked}
|
disabled={pluginSettings?.enableRightSideVolumeSwipe?.locked}
|
||||||
onValueChange={(enableRightSideVolumeSwipe) =>
|
onValueChange={(enableRightSideVolumeSwipe) =>
|
||||||
@@ -87,7 +87,7 @@ export const GestureControls: React.FC<Props> = ({ ...props }) => {
|
|||||||
)}
|
)}
|
||||||
disabled={pluginSettings?.hideVolumeSlider?.locked}
|
disabled={pluginSettings?.hideVolumeSlider?.locked}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.hideVolumeSlider}
|
value={settings.hideVolumeSlider}
|
||||||
disabled={pluginSettings?.hideVolumeSlider?.locked}
|
disabled={pluginSettings?.hideVolumeSlider?.locked}
|
||||||
onValueChange={(hideVolumeSlider) =>
|
onValueChange={(hideVolumeSlider) =>
|
||||||
@@ -103,7 +103,7 @@ export const GestureControls: React.FC<Props> = ({ ...props }) => {
|
|||||||
)}
|
)}
|
||||||
disabled={pluginSettings?.hideBrightnessSlider?.locked}
|
disabled={pluginSettings?.hideBrightnessSlider?.locked}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.hideBrightnessSlider}
|
value={settings.hideBrightnessSlider}
|
||||||
disabled={pluginSettings?.hideBrightnessSlider?.locked}
|
disabled={pluginSettings?.hideBrightnessSlider?.locked}
|
||||||
onValueChange={(hideBrightnessSlider) =>
|
onValueChange={(hideBrightnessSlider) =>
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ export const JellyseerrSettings = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [user] = useAtom(userAtom);
|
const [user] = useAtom(userAtom);
|
||||||
const { settings, updateSettings } = useSettings();
|
const { settings, updateSettings, pluginSettings } = useSettings();
|
||||||
|
// Only the server URL is admin-lockable — the password stays editable so
|
||||||
|
// the user can still sign in to the admin-pinned Jellyseerr server.
|
||||||
|
const urlLocked = pluginSettings?.jellyseerrServerUrl?.locked === true;
|
||||||
|
|
||||||
const [jellyseerrPassword, setJellyseerrPassword] = useState<
|
const [jellyseerrPassword, setJellyseerrPassword] = useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
@@ -30,22 +33,25 @@ export const JellyseerrSettings = () => {
|
|||||||
string | undefined
|
string | undefined
|
||||||
>(settings?.jellyseerrServerUrl || undefined);
|
>(settings?.jellyseerrServerUrl || undefined);
|
||||||
|
|
||||||
|
// When the URL is admin-pinned, ignore any locally-typed value so the login
|
||||||
|
// targets the same server the (read-only) input displays.
|
||||||
|
const effectiveServerUrl = urlLocked
|
||||||
|
? settings?.jellyseerrServerUrl
|
||||||
|
: jellyseerrServerUrl || settings?.jellyseerrServerUrl;
|
||||||
|
|
||||||
const loginToJellyseerrMutation = useMutation({
|
const loginToJellyseerrMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
if (!jellyseerrServerUrl && !settings?.jellyseerrServerUrl)
|
if (!effectiveServerUrl) throw new Error("Missing server url");
|
||||||
throw new Error("Missing server url");
|
|
||||||
if (!user?.Name)
|
if (!user?.Name)
|
||||||
throw new Error("Missing required information for login");
|
throw new Error("Missing required information for login");
|
||||||
const jellyseerrTempApi = new JellyseerrApi(
|
const jellyseerrTempApi = new JellyseerrApi(effectiveServerUrl);
|
||||||
jellyseerrServerUrl || settings.jellyseerrServerUrl || "",
|
|
||||||
);
|
|
||||||
const testResult = await jellyseerrTempApi.test();
|
const testResult = await jellyseerrTempApi.test();
|
||||||
if (!testResult.isValid) throw new Error("Invalid server url");
|
if (!testResult.isValid) throw new Error("Invalid server url");
|
||||||
return jellyseerrTempApi.login(user.Name, jellyseerrPassword || "");
|
return jellyseerrTempApi.login(user.Name, jellyseerrPassword || "");
|
||||||
},
|
},
|
||||||
onSuccess: (user) => {
|
onSuccess: (user) => {
|
||||||
setJellyseerrUser(user);
|
setJellyseerrUser(user);
|
||||||
updateSettings({ jellyseerrServerUrl });
|
updateSettings({ jellyseerrServerUrl: effectiveServerUrl });
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast.error(t("jellyseerr.failed_to_login"));
|
toast.error(t("jellyseerr.failed_to_login"));
|
||||||
@@ -115,30 +121,41 @@ export const JellyseerrSettings = () => {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<View className='flex flex-col rounded-xl overflow-hidden p-4 bg-neutral-900'>
|
<View className='flex flex-col rounded-xl overflow-hidden p-4 bg-neutral-900'>
|
||||||
<Text className='font-bold mb-1'>
|
<View style={{ opacity: urlLocked ? 0.5 : 1 }}>
|
||||||
{t("home.settings.plugins.jellyseerr.server_url")}
|
<Text className='font-bold mb-1'>
|
||||||
</Text>
|
{t("home.settings.plugins.jellyseerr.server_url")}
|
||||||
<View className='flex flex-col shrink mb-2'>
|
|
||||||
<Text className='text-xs text-gray-600'>
|
|
||||||
{t("home.settings.plugins.jellyseerr.server_url_hint")}
|
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
<View className='flex flex-col shrink mb-2'>
|
||||||
<Input
|
<Text className='text-xs text-gray-600'>
|
||||||
className='border border-neutral-800 mb-2'
|
{t("home.settings.plugins.jellyseerr.server_url_hint")}
|
||||||
placeholder={t(
|
</Text>
|
||||||
"home.settings.plugins.jellyseerr.server_url_placeholder",
|
</View>
|
||||||
|
<Input
|
||||||
|
className='border border-neutral-800 mb-2'
|
||||||
|
placeholder={t(
|
||||||
|
"home.settings.plugins.jellyseerr.server_url_placeholder",
|
||||||
|
)}
|
||||||
|
value={
|
||||||
|
urlLocked
|
||||||
|
? settings?.jellyseerrServerUrl
|
||||||
|
: (jellyseerrServerUrl ?? settings?.jellyseerrServerUrl)
|
||||||
|
}
|
||||||
|
defaultValue={
|
||||||
|
settings?.jellyseerrServerUrl ?? jellyseerrServerUrl
|
||||||
|
}
|
||||||
|
keyboardType='url'
|
||||||
|
returnKeyType='done'
|
||||||
|
autoCapitalize='none'
|
||||||
|
textContentType='URL'
|
||||||
|
onChangeText={setjellyseerrServerUrl}
|
||||||
|
editable={!urlLocked && !loginToJellyseerrMutation.isPending}
|
||||||
|
/>
|
||||||
|
{urlLocked && (
|
||||||
|
<Text className='text-xs text-red-600 mb-2'>
|
||||||
|
{t("home.settings.disabled_by_admin")}
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
value={jellyseerrServerUrl ?? settings?.jellyseerrServerUrl}
|
</View>
|
||||||
defaultValue={
|
|
||||||
settings?.jellyseerrServerUrl ?? jellyseerrServerUrl
|
|
||||||
}
|
|
||||||
keyboardType='url'
|
|
||||||
returnKeyType='done'
|
|
||||||
autoCapitalize='none'
|
|
||||||
textContentType='URL'
|
|
||||||
onChangeText={setjellyseerrServerUrl}
|
|
||||||
editable={!loginToJellyseerrMutation.isPending}
|
|
||||||
/>
|
|
||||||
<View>
|
<View>
|
||||||
<Text className='font-bold mb-2'>
|
<Text className='font-bold mb-2'>
|
||||||
{t("home.settings.plugins.jellyseerr.password")}
|
{t("home.settings.plugins.jellyseerr.password")}
|
||||||
|
|||||||
@@ -1,33 +1,28 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Switch, Text, View } from "react-native";
|
import { SettingSwitch } from "@/components/common/SettingSwitch";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
|
import { ListGroup } from "../list/ListGroup";
|
||||||
|
import { ListItem } from "../list/ListItem";
|
||||||
|
|
||||||
export const KefinTweaksSettings = () => {
|
export const KefinTweaksSettings = () => {
|
||||||
const { settings, updateSettings } = useSettings();
|
const { settings, updateSettings, pluginSettings } = useSettings();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const isEnabled = settings?.useKefinTweaks ?? false;
|
const isEnabled = settings?.useKefinTweaks ?? false;
|
||||||
|
const locked = pluginSettings?.useKefinTweaks?.locked === true;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className=''>
|
<ListGroup>
|
||||||
<View className='flex flex-col rounded-xl overflow-hidden p-4 bg-neutral-900'>
|
<ListItem
|
||||||
<Text className='text-xs text-red-600 mb-2'>
|
title={t("home.settings.plugins.kefinTweaks.watchlist_enabler")}
|
||||||
{t("home.settings.plugins.kefinTweaks.watchlist_enabler")}
|
disabledByAdmin={locked}
|
||||||
</Text>
|
>
|
||||||
|
<SettingSwitch
|
||||||
<View className='flex flex-row items-center justify-between mt-2'>
|
value={isEnabled}
|
||||||
<Text className='text-white'>
|
disabled={locked}
|
||||||
{isEnabled ? t("Watchlist On") : t("Watchlist Off")}
|
onValueChange={(value) => updateSettings({ useKefinTweaks: value })}
|
||||||
</Text>
|
/>
|
||||||
|
</ListItem>
|
||||||
<Switch
|
</ListGroup>
|
||||||
value={isEnabled}
|
|
||||||
onValueChange={(value) => updateSettings({ useKefinTweaks: value })}
|
|
||||||
trackColor={{ false: "#555", true: "purple" }}
|
|
||||||
thumbColor={isEnabled ? "#fff" : "#ccc"}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { Ionicons } from "@expo/vector-icons";
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Switch, TouchableOpacity, View } from "react-native";
|
import { TouchableOpacity, View } from "react-native";
|
||||||
import { toast } from "sonner-native";
|
import { toast } from "sonner-native";
|
||||||
|
import { SettingSwitch } from "@/components/common/SettingSwitch";
|
||||||
import { useWifiSSID } from "@/hooks/useWifiSSID";
|
import { useWifiSSID } from "@/hooks/useWifiSSID";
|
||||||
|
import { openLocationSettings } from "@/modules/wifi-ssid";
|
||||||
import { useServerUrl } from "@/providers/ServerUrlProvider";
|
import { useServerUrl } from "@/providers/ServerUrlProvider";
|
||||||
import { storage } from "@/utils/mmkv";
|
import { storage } from "@/utils/mmkv";
|
||||||
import {
|
import {
|
||||||
@@ -26,16 +28,26 @@ const DEFAULT_CONFIG: LocalNetworkConfig = {
|
|||||||
|
|
||||||
interface StatusDisplayProps {
|
interface StatusDisplayProps {
|
||||||
currentSSID: string | null;
|
currentSSID: string | null;
|
||||||
|
connectedToWifi: boolean;
|
||||||
isUsingLocalUrl: boolean;
|
isUsingLocalUrl: boolean;
|
||||||
|
locationBlocked: boolean;
|
||||||
|
onOpenLocationSettings: () => void;
|
||||||
t: (key: string) => string;
|
t: (key: string) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function StatusDisplay({
|
function StatusDisplay({
|
||||||
currentSSID,
|
currentSSID,
|
||||||
|
connectedToWifi,
|
||||||
isUsingLocalUrl,
|
isUsingLocalUrl,
|
||||||
|
locationBlocked,
|
||||||
|
onOpenLocationSettings,
|
||||||
t,
|
t,
|
||||||
}: StatusDisplayProps): React.ReactElement {
|
}: StatusDisplayProps): React.ReactElement {
|
||||||
const wifiStatus = currentSSID ?? t("home.settings.network.not_connected");
|
const wifiStatus = currentSSID
|
||||||
|
? currentSSID
|
||||||
|
: connectedToWifi
|
||||||
|
? t("home.settings.network.ssid_hidden")
|
||||||
|
: t("home.settings.network.not_connected");
|
||||||
const urlType = isUsingLocalUrl
|
const urlType = isUsingLocalUrl
|
||||||
? t("home.settings.network.local")
|
? t("home.settings.network.local")
|
||||||
: t("home.settings.network.remote");
|
: t("home.settings.network.remote");
|
||||||
@@ -55,6 +67,23 @@ function StatusDisplay({
|
|||||||
</Text>
|
</Text>
|
||||||
<Text className={urlTypeColor}>{urlType}</Text>
|
<Text className={urlTypeColor}>{urlType}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{locationBlocked && (
|
||||||
|
<View className='mt-2 pt-2 border-t border-neutral-800'>
|
||||||
|
<Text className='text-xs text-amber-400'>
|
||||||
|
{t("home.settings.network.location_off_description")}
|
||||||
|
</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={onOpenLocationSettings}
|
||||||
|
className='mt-2 self-start'
|
||||||
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||||
|
>
|
||||||
|
<Text className='text-xs text-blue-400 font-semibold'>
|
||||||
|
{t("home.settings.network.open_location_settings")}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -62,7 +91,17 @@ function StatusDisplay({
|
|||||||
export function LocalNetworkSettings(): React.ReactElement | null {
|
export function LocalNetworkSettings(): React.ReactElement | null {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { permissionStatus, requestPermission } = useWifiSSID();
|
const { permissionStatus, requestPermission } = useWifiSSID();
|
||||||
const { isUsingLocalUrl, currentSSID, refreshUrlState } = useServerUrl();
|
const { isUsingLocalUrl, currentSSID, connectedToWifi, refreshUrlState } =
|
||||||
|
useServerUrl();
|
||||||
|
|
||||||
|
// Connected to Wi-Fi and have permission, but the OS won't reveal the SSID —
|
||||||
|
// on Android this means device Location services are off.
|
||||||
|
const locationBlocked =
|
||||||
|
permissionStatus === "granted" && connectedToWifi && !currentSSID;
|
||||||
|
|
||||||
|
const handleOpenLocationSettings = useCallback(() => {
|
||||||
|
openLocationSettings();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const remoteUrl = storage.getString("serverUrl");
|
const remoteUrl = storage.getString("serverUrl");
|
||||||
const [config, setConfig] = useState<LocalNetworkConfig>(DEFAULT_CONFIG);
|
const [config, setConfig] = useState<LocalNetworkConfig>(DEFAULT_CONFIG);
|
||||||
@@ -147,7 +186,10 @@ export function LocalNetworkSettings(): React.ReactElement | null {
|
|||||||
title={t("home.settings.network.auto_switch_enabled")}
|
title={t("home.settings.network.auto_switch_enabled")}
|
||||||
subtitle={t("home.settings.network.auto_switch_description")}
|
subtitle={t("home.settings.network.auto_switch_description")}
|
||||||
>
|
>
|
||||||
<Switch value={config.enabled} onValueChange={handleToggleEnabled} />
|
<SettingSwitch
|
||||||
|
value={config.enabled}
|
||||||
|
onValueChange={handleToggleEnabled}
|
||||||
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
|
|
||||||
@@ -195,18 +237,23 @@ export function LocalNetworkSettings(): React.ReactElement | null {
|
|||||||
)}
|
)}
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
|
|
||||||
<View className='py-2'>
|
{!locationBlocked && (
|
||||||
<Button
|
<View className='py-2'>
|
||||||
onPress={handleAddCurrentNetwork}
|
<Button
|
||||||
disabled={!currentSSID || permissionStatus !== "granted"}
|
onPress={handleAddCurrentNetwork}
|
||||||
>
|
disabled={!currentSSID || permissionStatus !== "granted"}
|
||||||
{addNetworkButtonText}
|
>
|
||||||
</Button>
|
{addNetworkButtonText}
|
||||||
</View>
|
</Button>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
<StatusDisplay
|
<StatusDisplay
|
||||||
currentSSID={currentSSID}
|
currentSSID={currentSSID}
|
||||||
|
connectedToWifi={connectedToWifi}
|
||||||
isUsingLocalUrl={isUsingLocalUrl}
|
isUsingLocalUrl={isUsingLocalUrl}
|
||||||
|
locationBlocked={locationBlocked}
|
||||||
|
onOpenLocationSettings={handleOpenLocationSettings}
|
||||||
t={t}
|
t={t}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Platform, Switch, View, type ViewProps } from "react-native";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Platform, View, type ViewProps } from "react-native";
|
||||||
|
import { SettingSwitch } from "@/components/common/SettingSwitch";
|
||||||
import { Stepper } from "@/components/inputs/Stepper";
|
import { Stepper } from "@/components/inputs/Stepper";
|
||||||
import { Text } from "../common/Text";
|
import { Text } from "../common/Text";
|
||||||
import { ListGroup } from "../list/ListGroup";
|
import { ListGroup } from "../list/ListGroup";
|
||||||
@@ -17,20 +19,21 @@ export const MpvSubtitleSettings: React.FC<Props> = ({ ...props }) => {
|
|||||||
const isTv = Platform.isTV;
|
const isTv = Platform.isTV;
|
||||||
const media = useMedia();
|
const media = useMedia();
|
||||||
const { settings, updateSettings } = media;
|
const { settings, updateSettings } = media;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const alignXOptions: AlignX[] = ["left", "center", "right"];
|
const alignXOptions: AlignX[] = ["left", "center", "right"];
|
||||||
const alignYOptions: AlignY[] = ["top", "center", "bottom"];
|
const alignYOptions: AlignY[] = ["top", "center", "bottom"];
|
||||||
|
|
||||||
const alignXLabels: Record<AlignX, string> = {
|
const alignXLabels: Record<AlignX, string> = {
|
||||||
left: "Left",
|
left: t("home.settings.subtitles.align.left"),
|
||||||
center: "Center",
|
center: t("home.settings.subtitles.align.center"),
|
||||||
right: "Right",
|
right: t("home.settings.subtitles.align.right"),
|
||||||
};
|
};
|
||||||
|
|
||||||
const alignYLabels: Record<AlignY, string> = {
|
const alignYLabels: Record<AlignY, string> = {
|
||||||
top: "Top",
|
top: t("home.settings.subtitles.align.top"),
|
||||||
center: "Center",
|
center: t("home.settings.subtitles.align.center"),
|
||||||
bottom: "Bottom",
|
bottom: t("home.settings.subtitles.align.bottom"),
|
||||||
};
|
};
|
||||||
|
|
||||||
const alignXOptionGroups = useMemo(() => {
|
const alignXOptionGroups = useMemo(() => {
|
||||||
@@ -60,16 +63,18 @@ export const MpvSubtitleSettings: React.FC<Props> = ({ ...props }) => {
|
|||||||
return (
|
return (
|
||||||
<View {...props}>
|
<View {...props}>
|
||||||
<ListGroup
|
<ListGroup
|
||||||
title='MPV Subtitle Settings'
|
title={t("home.settings.subtitles.mpv_settings_title")}
|
||||||
description={
|
description={
|
||||||
<Text className='text-[#8E8D91] text-xs'>
|
<Text className='text-[#8E8D91] text-xs'>
|
||||||
Advanced subtitle customization for MPV player
|
{t("home.settings.subtitles.mpv_settings_description")}
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{!isTv && (
|
{!isTv && (
|
||||||
<>
|
<>
|
||||||
<ListItem title='Vertical Margin'>
|
<ListItem
|
||||||
|
title={t("home.settings.subtitles.mpv_subtitle_margin_y")}
|
||||||
|
>
|
||||||
<Stepper
|
<Stepper
|
||||||
value={settings.mpvSubtitleMarginY ?? 0}
|
value={settings.mpvSubtitleMarginY ?? 0}
|
||||||
step={5}
|
step={5}
|
||||||
@@ -81,7 +86,7 @@ export const MpvSubtitleSettings: React.FC<Props> = ({ ...props }) => {
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem title='Horizontal Alignment'>
|
<ListItem title={t("home.settings.subtitles.mpv_subtitle_align_x")}>
|
||||||
<PlatformDropdown
|
<PlatformDropdown
|
||||||
groups={alignXOptionGroups}
|
groups={alignXOptionGroups}
|
||||||
trigger={
|
trigger={
|
||||||
@@ -96,11 +101,11 @@ export const MpvSubtitleSettings: React.FC<Props> = ({ ...props }) => {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
title='Horizontal Alignment'
|
title={t("home.settings.subtitles.mpv_subtitle_align_x")}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem title='Vertical Alignment'>
|
<ListItem title={t("home.settings.subtitles.mpv_subtitle_align_y")}>
|
||||||
<PlatformDropdown
|
<PlatformDropdown
|
||||||
groups={alignYOptionGroups}
|
groups={alignYOptionGroups}
|
||||||
trigger={
|
trigger={
|
||||||
@@ -115,14 +120,14 @@ export const MpvSubtitleSettings: React.FC<Props> = ({ ...props }) => {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
title='Vertical Alignment'
|
title={t("home.settings.subtitles.mpv_subtitle_align_y")}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ListItem title='Opaque Background'>
|
<ListItem title={t("home.settings.subtitles.opaque_background")}>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.mpvSubtitleBackgroundEnabled ?? false}
|
value={settings.mpvSubtitleBackgroundEnabled ?? false}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
updateSettings({ mpvSubtitleBackgroundEnabled: value })
|
updateSettings({ mpvSubtitleBackgroundEnabled: value })
|
||||||
@@ -131,7 +136,7 @@ export const MpvSubtitleSettings: React.FC<Props> = ({ ...props }) => {
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{settings.mpvSubtitleBackgroundEnabled && (
|
{settings.mpvSubtitleBackgroundEnabled && (
|
||||||
<ListItem title='Background Opacity'>
|
<ListItem title={t("home.settings.subtitles.background_opacity")}>
|
||||||
<Stepper
|
<Stepper
|
||||||
value={settings.mpvSubtitleBackgroundOpacity ?? 75}
|
value={settings.mpvSubtitleBackgroundOpacity ?? 75}
|
||||||
step={5}
|
step={5}
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import { TFunction } from "i18next";
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Linking, Switch, View } from "react-native";
|
import { Linking, View } from "react-native";
|
||||||
import { BITRATES } from "@/components/BitrateSelector";
|
import { BITRATES } from "@/components/BitrateSelector";
|
||||||
|
import { SettingSwitch } from "@/components/common/SettingSwitch";
|
||||||
import { PlatformDropdown } from "@/components/PlatformDropdown";
|
import { PlatformDropdown } from "@/components/PlatformDropdown";
|
||||||
import DisabledSetting from "@/components/settings/DisabledSetting";
|
import DisabledSetting from "@/components/settings/DisabledSetting";
|
||||||
import useRouter from "@/hooks/useAppRouter";
|
import useRouter from "@/hooks/useAppRouter";
|
||||||
@@ -132,7 +133,7 @@ export const OtherSettings: React.FC = () => {
|
|||||||
title={t("home.settings.other.safe_area_in_controls")}
|
title={t("home.settings.other.safe_area_in_controls")}
|
||||||
disabled={pluginSettings?.safeAreaInControlsEnabled?.locked}
|
disabled={pluginSettings?.safeAreaInControlsEnabled?.locked}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.safeAreaInControlsEnabled}
|
value={settings.safeAreaInControlsEnabled}
|
||||||
disabled={pluginSettings?.safeAreaInControlsEnabled?.locked}
|
disabled={pluginSettings?.safeAreaInControlsEnabled?.locked}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
@@ -150,7 +151,7 @@ export const OtherSettings: React.FC = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.showCustomMenuLinks}
|
value={settings.showCustomMenuLinks}
|
||||||
disabled={pluginSettings?.showCustomMenuLinks?.locked}
|
disabled={pluginSettings?.showCustomMenuLinks?.locked}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
@@ -188,7 +189,7 @@ export const OtherSettings: React.FC = () => {
|
|||||||
title={t("home.settings.other.disable_haptic_feedback")}
|
title={t("home.settings.other.disable_haptic_feedback")}
|
||||||
disabled={pluginSettings?.disableHapticFeedback?.locked}
|
disabled={pluginSettings?.disableHapticFeedback?.locked}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.disableHapticFeedback}
|
value={settings.disableHapticFeedback}
|
||||||
disabled={pluginSettings?.disableHapticFeedback?.locked}
|
disabled={pluginSettings?.disableHapticFeedback?.locked}
|
||||||
onValueChange={(disableHapticFeedback) =>
|
onValueChange={(disableHapticFeedback) =>
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import { TFunction } from "i18next";
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Switch, View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { BITRATES } from "@/components/BitrateSelector";
|
import { BITRATES } from "@/components/BitrateSelector";
|
||||||
|
import { SettingSwitch } from "@/components/common/SettingSwitch";
|
||||||
import { PlatformDropdown } from "@/components/PlatformDropdown";
|
import { PlatformDropdown } from "@/components/PlatformDropdown";
|
||||||
import { PLAYBACK_SPEEDS } from "@/components/PlaybackSpeedSelector";
|
import { PLAYBACK_SPEEDS } from "@/components/PlaybackSpeedSelector";
|
||||||
import DisabledSetting from "@/components/settings/DisabledSetting";
|
import DisabledSetting from "@/components/settings/DisabledSetting";
|
||||||
@@ -115,7 +116,7 @@ export const PlaybackControlsSettings: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DisabledSetting disabled={disabled}>
|
<DisabledSetting disabled={disabled}>
|
||||||
<ListGroup title={t("home.settings.other.other_title")} className=''>
|
<ListGroup title={t("home.settings.other.other_title")} className='mb-4'>
|
||||||
<ListItem
|
<ListItem
|
||||||
title={t("home.settings.other.video_orientation")}
|
title={t("home.settings.other.video_orientation")}
|
||||||
disabled={pluginSettings?.defaultVideoOrientation?.locked}
|
disabled={pluginSettings?.defaultVideoOrientation?.locked}
|
||||||
@@ -146,7 +147,7 @@ export const PlaybackControlsSettings: React.FC = () => {
|
|||||||
title={t("home.settings.other.safe_area_in_controls")}
|
title={t("home.settings.other.safe_area_in_controls")}
|
||||||
disabled={pluginSettings?.safeAreaInControlsEnabled?.locked}
|
disabled={pluginSettings?.safeAreaInControlsEnabled?.locked}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.safeAreaInControlsEnabled}
|
value={settings.safeAreaInControlsEnabled}
|
||||||
disabled={pluginSettings?.safeAreaInControlsEnabled?.locked}
|
disabled={pluginSettings?.safeAreaInControlsEnabled?.locked}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
@@ -205,7 +206,7 @@ export const PlaybackControlsSettings: React.FC = () => {
|
|||||||
title={t("home.settings.other.disable_haptic_feedback")}
|
title={t("home.settings.other.disable_haptic_feedback")}
|
||||||
disabled={pluginSettings?.disableHapticFeedback?.locked}
|
disabled={pluginSettings?.disableHapticFeedback?.locked}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.disableHapticFeedback}
|
value={settings.disableHapticFeedback}
|
||||||
disabled={pluginSettings?.disableHapticFeedback?.locked}
|
disabled={pluginSettings?.disableHapticFeedback?.locked}
|
||||||
onValueChange={(disableHapticFeedback) =>
|
onValueChange={(disableHapticFeedback) =>
|
||||||
@@ -218,7 +219,7 @@ export const PlaybackControlsSettings: React.FC = () => {
|
|||||||
title={t("home.settings.other.auto_play_next_episode")}
|
title={t("home.settings.other.auto_play_next_episode")}
|
||||||
disabled={pluginSettings?.autoPlayNextEpisode?.locked}
|
disabled={pluginSettings?.autoPlayNextEpisode?.locked}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.autoPlayNextEpisode}
|
value={settings.autoPlayNextEpisode}
|
||||||
disabled={pluginSettings?.autoPlayNextEpisode?.locked}
|
disabled={pluginSettings?.autoPlayNextEpisode?.locked}
|
||||||
onValueChange={(autoPlayNextEpisode) =>
|
onValueChange={(autoPlayNextEpisode) =>
|
||||||
|
|||||||
@@ -20,12 +20,7 @@ export const PluginSettings = () => {
|
|||||||
>
|
>
|
||||||
<ListItem
|
<ListItem
|
||||||
onPress={() => router.push("/settings/plugins/jellyseerr/page")}
|
onPress={() => router.push("/settings/plugins/jellyseerr/page")}
|
||||||
title={"Jellyseerr"}
|
title='Jellyseerr'
|
||||||
showArrow
|
|
||||||
/>
|
|
||||||
<ListItem
|
|
||||||
onPress={() => router.push("/settings/plugins/marlin-search/page")}
|
|
||||||
title='Marlin Search'
|
|
||||||
showArrow
|
showArrow
|
||||||
/>
|
/>
|
||||||
<ListItem
|
<ListItem
|
||||||
@@ -33,6 +28,11 @@ export const PluginSettings = () => {
|
|||||||
title='Streamystats'
|
title='Streamystats'
|
||||||
showArrow
|
showArrow
|
||||||
/>
|
/>
|
||||||
|
<ListItem
|
||||||
|
onPress={() => router.push("/settings/plugins/marlin-search/page")}
|
||||||
|
title='Marlin Search'
|
||||||
|
showArrow
|
||||||
|
/>
|
||||||
<ListItem
|
<ListItem
|
||||||
onPress={() => router.push("/settings/plugins/kefinTweaks/page")}
|
onPress={() => router.push("/settings/plugins/kefinTweaks/page")}
|
||||||
title='KefinTweaks'
|
title='KefinTweaks'
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Feather } from "@expo/vector-icons";
|
||||||
import {
|
import {
|
||||||
BottomSheetBackdrop,
|
BottomSheetBackdrop,
|
||||||
type BottomSheetBackdropProps,
|
type BottomSheetBackdropProps,
|
||||||
@@ -5,11 +6,13 @@ import {
|
|||||||
BottomSheetView,
|
BottomSheetView,
|
||||||
} from "@gorhom/bottom-sheet";
|
} from "@gorhom/bottom-sheet";
|
||||||
import { getQuickConnectApi } from "@jellyfin/sdk/lib/utils/api";
|
import { getQuickConnectApi } from "@jellyfin/sdk/lib/utils/api";
|
||||||
|
import { requireOptionalNativeModule } from "expo-modules-core";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useCallback, useMemo, useRef, useState } from "react";
|
import { useCallback, useMemo, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Alert, Platform, View, type ViewProps } from "react-native";
|
import { Alert, Platform, View, type ViewProps } from "react-native";
|
||||||
|
import { Pressable } from "react-native-gesture-handler";
|
||||||
import { useHaptic } from "@/hooks/useHaptic";
|
import { useHaptic } from "@/hooks/useHaptic";
|
||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
import { Button } from "../Button";
|
import { Button } from "../Button";
|
||||||
@@ -79,6 +82,15 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
|
|||||||
}
|
}
|
||||||
}, [api, user, quickConnectCode]);
|
}, [api, user, quickConnectCode]);
|
||||||
|
|
||||||
|
const pasteCode = useCallback(async () => {
|
||||||
|
// Builds without the expo-clipboard native module: probe first (no-op).
|
||||||
|
if (!requireOptionalNativeModule("ExpoClipboard")) return;
|
||||||
|
const Clipboard = await import("expo-clipboard");
|
||||||
|
const text = await Clipboard.getStringAsync();
|
||||||
|
const digits = (text || "").replace(/\D/g, "").slice(0, 6);
|
||||||
|
if (digits) setQuickConnectCode(digits);
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (isTv) return null;
|
if (isTv) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -130,6 +142,15 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
|
|||||||
style={{ paddingHorizontal: 16 }}
|
style={{ paddingHorizontal: 16 }}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
<Pressable
|
||||||
|
onPress={pasteCode}
|
||||||
|
className='flex-row items-center justify-center self-center'
|
||||||
|
>
|
||||||
|
<Feather name='clipboard' size={15} color='#a3a3a3' />
|
||||||
|
<Text className='text-neutral-400 ml-2'>
|
||||||
|
{t("home.settings.quick_connect.paste_code")}
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export const StorageSettings = () => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{!Platform.isTV && (
|
{!Platform.isTV && (
|
||||||
<ListGroup>
|
<ListGroup className={Platform.OS === "android" ? "mt-4" : undefined}>
|
||||||
<ListItem
|
<ListItem
|
||||||
textColor='red'
|
textColor='red'
|
||||||
onPress={onDeleteClicked}
|
onPress={onDeleteClicked}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { SubtitlePlaybackMode } from "@jellyfin/sdk/lib/generated-client";
|
|||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Platform, View, type ViewProps } from "react-native";
|
import { Platform, View, type ViewProps } from "react-native";
|
||||||
import { Switch } from "react-native-gesture-handler";
|
|
||||||
import { Input } from "@/components/common/Input";
|
import { Input } from "@/components/common/Input";
|
||||||
|
import { SettingSwitch } from "@/components/common/SettingSwitch";
|
||||||
import { Stepper } from "@/components/inputs/Stepper";
|
import { Stepper } from "@/components/inputs/Stepper";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
import { Text } from "../common/Text";
|
import { Text } from "../common/Text";
|
||||||
@@ -98,6 +98,7 @@ export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
|
|||||||
return (
|
return (
|
||||||
<View {...props}>
|
<View {...props}>
|
||||||
<ListGroup
|
<ListGroup
|
||||||
|
className='mb-4'
|
||||||
title={t("home.settings.subtitles.subtitle_title")}
|
title={t("home.settings.subtitles.subtitle_title")}
|
||||||
description={
|
description={
|
||||||
<Text className='text-[#8E8D91] text-xs'>
|
<Text className='text-[#8E8D91] text-xs'>
|
||||||
@@ -152,7 +153,7 @@ export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
|
|||||||
title={t("home.settings.subtitles.set_subtitle_track")}
|
title={t("home.settings.subtitles.set_subtitle_track")}
|
||||||
disabled={pluginSettings?.rememberSubtitleSelections?.locked}
|
disabled={pluginSettings?.rememberSubtitleSelections?.locked}
|
||||||
>
|
>
|
||||||
<Switch
|
<SettingSwitch
|
||||||
value={settings.rememberSubtitleSelections}
|
value={settings.rememberSubtitleSelections}
|
||||||
disabled={pluginSettings?.rememberSubtitleSelections?.locked}
|
disabled={pluginSettings?.rememberSubtitleSelections?.locked}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Animated,
|
Animated,
|
||||||
@@ -28,6 +29,7 @@ export const TVSubtitleResultCard = React.forwardRef<
|
|||||||
const styles = createStyles(typography);
|
const styles = createStyles(typography);
|
||||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||||
useTVFocusAnimation({ scaleAmount: 1.03 });
|
useTVFocusAnimation({ scaleAmount: 1.03 });
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
@@ -152,7 +154,7 @@ export const TVSubtitleResultCard = React.forwardRef<
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Text style={styles.flagText}>Hash Match</Text>
|
<Text style={styles.flagText}>{t("player.hash_match")}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{result.hearingImpaired && (
|
{result.hearingImpaired && (
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ export const BottomControls: FC<BottomControlsProps> = ({
|
|||||||
<SkipButton
|
<SkipButton
|
||||||
showButton={showSkipButton}
|
showButton={showSkipButton}
|
||||||
onPress={skipIntro}
|
onPress={skipIntro}
|
||||||
buttonText='Skip Intro'
|
buttonText={t("player.skip_intro")}
|
||||||
/>
|
/>
|
||||||
{/* Smart Skip Credits behavior:
|
{/* Smart Skip Credits behavior:
|
||||||
- Show "Skip Credits" if there's content after credits OR no next episode
|
- Show "Skip Credits" if there's content after credits OR no next episode
|
||||||
@@ -193,7 +193,7 @@ export const BottomControls: FC<BottomControlsProps> = ({
|
|||||||
showSkipCreditButton && (hasContentAfterCredits || !nextItem)
|
showSkipCreditButton && (hasContentAfterCredits || !nextItem)
|
||||||
}
|
}
|
||||||
onPress={skipCredit}
|
onPress={skipCredit}
|
||||||
buttonText='Skip Credits'
|
buttonText={t("player.skip_credits")}
|
||||||
/>
|
/>
|
||||||
{settings.autoPlayNextEpisode !== false &&
|
{settings.autoPlayNextEpisode !== false &&
|
||||||
(settings.maxAutoPlayEpisodeCount.value === -1 ||
|
(settings.maxAutoPlayEpisodeCount.value === -1 ||
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const ContinueWatchingOverlay: React.FC<ContinueWatchingOverlayProps> = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Text className='text-2xl font-bold text-white py-4 '>
|
<Text className='text-2xl font-bold text-white py-4 '>
|
||||||
Are you still watching ?
|
{t("player.still_watching")}
|
||||||
</Text>
|
</Text>
|
||||||
<Button
|
<Button
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import type {
|
|||||||
BaseItemDto,
|
BaseItemDto,
|
||||||
MediaSourceInfo,
|
MediaSourceInfo,
|
||||||
} from "@jellyfin/sdk/lib/generated-client";
|
} from "@jellyfin/sdk/lib/generated-client";
|
||||||
|
import { useKeyEventListener } from "expo-key-event";
|
||||||
import { useLocalSearchParams } from "expo-router";
|
import { useLocalSearchParams } from "expo-router";
|
||||||
import { type FC, useCallback, useEffect, useState } from "react";
|
import { type FC, useCallback, useEffect, useState } from "react";
|
||||||
import { StyleSheet, useWindowDimensions, View } from "react-native";
|
import { Platform, StyleSheet, useWindowDimensions, View } from "react-native";
|
||||||
import Animated, {
|
import Animated, {
|
||||||
Easing,
|
Easing,
|
||||||
type SharedValue,
|
type SharedValue,
|
||||||
@@ -205,6 +206,22 @@ export const Controls: FC<Props> = ({
|
|||||||
play,
|
play,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useKeyEventListener((e) => {
|
||||||
|
if (episodeView || showAudioSlider) return;
|
||||||
|
if (e?.eventType !== "press") return;
|
||||||
|
const key = e.key;
|
||||||
|
|
||||||
|
if (key === " " || key === "Spacebar" || key === "Space") {
|
||||||
|
togglePlay();
|
||||||
|
} else if (!Platform.isTV && key === "ArrowLeft") {
|
||||||
|
// Exclude TV platforms to prevent conflicts with the remote control,
|
||||||
|
// which uses the same arrow keys for directional UI navigation.
|
||||||
|
handleSkipBackward();
|
||||||
|
} else if (!Platform.isTV && key === "ArrowRight") {
|
||||||
|
handleSkipForward();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Time management hook
|
// Time management hook
|
||||||
const { currentTime, remainingTime } = useVideoTime({
|
const { currentTime, remainingTime } = useVideoTime({
|
||||||
progress,
|
progress,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type {
|
|||||||
MediaSourceInfo,
|
MediaSourceInfo,
|
||||||
} from "@jellyfin/sdk/lib/generated-client";
|
} from "@jellyfin/sdk/lib/generated-client";
|
||||||
import { type FC, useCallback, useState } from "react";
|
import { type FC, useCallback, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { Platform, TouchableOpacity, View } from "react-native";
|
import { Platform, TouchableOpacity, View } from "react-native";
|
||||||
import useRouter from "@/hooks/useAppRouter";
|
import useRouter from "@/hooks/useAppRouter";
|
||||||
import { useControlsSafeAreaInsets } from "@/hooks/useControlsSafeAreaInsets";
|
import { useControlsSafeAreaInsets } from "@/hooks/useControlsSafeAreaInsets";
|
||||||
@@ -57,6 +58,7 @@ export const HeaderControls: FC<HeaderControlsProps> = ({
|
|||||||
showTechnicalInfo = false,
|
showTechnicalInfo = false,
|
||||||
onToggleTechnicalInfo,
|
onToggleTechnicalInfo,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const insets = useControlsSafeAreaInsets();
|
const insets = useControlsSafeAreaInsets();
|
||||||
const lightHapticFeedback = useHaptic("light");
|
const lightHapticFeedback = useHaptic("light");
|
||||||
@@ -127,8 +129,8 @@ export const HeaderControls: FC<HeaderControlsProps> = ({
|
|||||||
onPress={toggleOrientation}
|
onPress={toggleOrientation}
|
||||||
disabled={isTogglingOrientation}
|
disabled={isTogglingOrientation}
|
||||||
className='aspect-square flex flex-col rounded-xl items-center justify-center p-2'
|
className='aspect-square flex flex-col rounded-xl items-center justify-center p-2'
|
||||||
accessibilityLabel='Toggle screen orientation'
|
accessibilityLabel={t("accessibility.toggle_orientation")}
|
||||||
accessibilityHint='Toggles the screen orientation between portrait and landscape'
|
accessibilityHint={t("accessibility.toggle_orientation_hint")}
|
||||||
>
|
>
|
||||||
<MaterialIcons
|
<MaterialIcons
|
||||||
name='screen-rotation'
|
name='screen-rotation'
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { Platform, StyleSheet, Text, View } from "react-native";
|
import { Platform, StyleSheet, Text, View } from "react-native";
|
||||||
import Animated, {
|
import Animated, {
|
||||||
Easing,
|
Easing,
|
||||||
@@ -184,6 +185,7 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
|
|||||||
currentAudioIndex,
|
currentAudioIndex,
|
||||||
}) => {
|
}) => {
|
||||||
const typography = useScaledTVTypography();
|
const typography = useScaledTVTypography();
|
||||||
|
const { t } = useTranslation();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const safeInsets = useControlsSafeAreaInsets();
|
const safeInsets = useControlsSafeAreaInsets();
|
||||||
const [info, setInfo] = useState<TechnicalInfo | null>(null);
|
const [info, setInfo] = useState<TechnicalInfo | null>(null);
|
||||||
@@ -312,13 +314,13 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
|
|||||||
)}
|
)}
|
||||||
{info?.videoCodec && (
|
{info?.videoCodec && (
|
||||||
<Text style={textStyle}>
|
<Text style={textStyle}>
|
||||||
Video: {formatCodec(info.videoCodec)}
|
{t("player.technical_info.video")} {formatCodec(info.videoCodec)}
|
||||||
{info.fps ? ` @ ${formatFps(info.fps)} fps` : ""}
|
{info.fps ? ` @ ${formatFps(info.fps)} fps` : ""}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{info?.audioCodec && (
|
{info?.audioCodec && (
|
||||||
<Text style={textStyle}>
|
<Text style={textStyle}>
|
||||||
Audio: {formatCodec(info.audioCodec)}
|
{t("player.technical_info.audio")} {formatCodec(info.audioCodec)}
|
||||||
{streamInfo?.audioChannels
|
{streamInfo?.audioChannels
|
||||||
? ` ${formatAudioChannels(streamInfo.audioChannels)}`
|
? ` ${formatAudioChannels(streamInfo.audioChannels)}`
|
||||||
: ""}
|
: ""}
|
||||||
@@ -326,12 +328,13 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
|
|||||||
)}
|
)}
|
||||||
{streamInfo?.subtitleCodec && (
|
{streamInfo?.subtitleCodec && (
|
||||||
<Text style={textStyle}>
|
<Text style={textStyle}>
|
||||||
Subtitle: {formatCodec(streamInfo.subtitleCodec)}
|
{t("player.technical_info.subtitle")}{" "}
|
||||||
|
{formatCodec(streamInfo.subtitleCodec)}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{(info?.videoBitrate || info?.audioBitrate) && (
|
{(info?.videoBitrate || info?.audioBitrate) && (
|
||||||
<Text style={textStyle}>
|
<Text style={textStyle}>
|
||||||
Bitrate:{" "}
|
{t("player.technical_info.bitrate")}{" "}
|
||||||
{info.videoBitrate
|
{info.videoBitrate
|
||||||
? formatBitrate(info.videoBitrate)
|
? formatBitrate(info.videoBitrate)
|
||||||
: info.audioBitrate
|
: info.audioBitrate
|
||||||
@@ -341,7 +344,9 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
|
|||||||
)}
|
)}
|
||||||
{info?.cacheSeconds !== undefined && (
|
{info?.cacheSeconds !== undefined && (
|
||||||
<Text style={textStyle}>
|
<Text style={textStyle}>
|
||||||
Buffer: {info.cacheSeconds.toFixed(1)}s
|
{t("player.technical_info.buffer_seconds", {
|
||||||
|
seconds: info.cacheSeconds.toFixed(1),
|
||||||
|
})}
|
||||||
{info?.demuxerMaxBytes !== undefined
|
{info?.demuxerMaxBytes !== undefined
|
||||||
? ` (cap ${info.demuxerMaxBytes}MB` +
|
? ` (cap ${info.demuxerMaxBytes}MB` +
|
||||||
`${info.demuxerMaxBackBytes !== undefined ? ` / ${info.demuxerMaxBackBytes}MB back` : ""}` +
|
`${info.demuxerMaxBackBytes !== undefined ? ` / ${info.demuxerMaxBackBytes}MB back` : ""}` +
|
||||||
@@ -352,7 +357,7 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
|
|||||||
)}
|
)}
|
||||||
{info?.voDriver && (
|
{info?.voDriver && (
|
||||||
<Text style={textStyle}>
|
<Text style={textStyle}>
|
||||||
VO: {info.voDriver}
|
{t("player.technical_info.vo")} {info.voDriver}
|
||||||
{info.hwdec ? ` / ${info.hwdec}` : ""}
|
{info.hwdec ? ` / ${info.hwdec}` : ""}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@@ -364,10 +369,14 @@ export const TechnicalInfoOverlay: FC<TechnicalInfoOverlayProps> = memo(
|
|||||||
)}
|
)}
|
||||||
{info?.droppedFrames !== undefined && info.droppedFrames > 0 && (
|
{info?.droppedFrames !== undefined && info.droppedFrames > 0 && (
|
||||||
<Text style={[textStyle, styles.warningText]}>
|
<Text style={[textStyle, styles.warningText]}>
|
||||||
Dropped: {info.droppedFrames} frames
|
{t("player.technical_info.dropped_frames", {
|
||||||
|
count: info.droppedFrames,
|
||||||
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{!info && !playMethod && <Text style={textStyle}>Loading...</Text>}
|
{!info && !playMethod && (
|
||||||
|
<Text style={textStyle}>{t("player.technical_info.loading")}</Text>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { Platform, View } from "react-native";
|
import { Platform, View } from "react-native";
|
||||||
import {
|
import {
|
||||||
type OptionGroup,
|
type OptionGroup,
|
||||||
@@ -54,6 +55,7 @@ export const AspectRatioSelector: React.FC<AspectRatioSelectorProps> = ({
|
|||||||
onRatioChange,
|
onRatioChange,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const lightHapticFeedback = useHaptic("light");
|
const lightHapticFeedback = useHaptic("light");
|
||||||
|
|
||||||
const handleRatioSelect = (ratio: AspectRatio) => {
|
const handleRatioSelect = (ratio: AspectRatio) => {
|
||||||
@@ -66,7 +68,10 @@ export const AspectRatioSelector: React.FC<AspectRatioSelectorProps> = ({
|
|||||||
{
|
{
|
||||||
options: ASPECT_RATIO_OPTIONS.map((option) => ({
|
options: ASPECT_RATIO_OPTIONS.map((option) => ({
|
||||||
type: "radio" as const,
|
type: "radio" as const,
|
||||||
label: option.label,
|
label:
|
||||||
|
option.id === "default"
|
||||||
|
? t("player.aspect_ratio_original")
|
||||||
|
: option.label,
|
||||||
value: option.id,
|
value: option.id,
|
||||||
selected: option.id === currentRatio,
|
selected: option.id === currentRatio,
|
||||||
onPress: () => handleRatioSelect(option.id),
|
onPress: () => handleRatioSelect(option.id),
|
||||||
@@ -94,7 +99,7 @@ export const AspectRatioSelector: React.FC<AspectRatioSelectorProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PlatformDropdown
|
<PlatformDropdown
|
||||||
title='Aspect Ratio'
|
title={t("player.aspect_ratio")}
|
||||||
groups={optionGroups}
|
groups={optionGroups}
|
||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
bottomSheetConfig={{
|
bottomSheetConfig={{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { useLocalSearchParams } from "expo-router";
|
import { useLocalSearchParams } from "expo-router";
|
||||||
import { useCallback, useMemo, useRef } from "react";
|
import { useCallback, useMemo, useRef } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { Platform, View } from "react-native";
|
import { Platform, View } from "react-native";
|
||||||
import { BITRATES } from "@/components/BitrateSelector";
|
import { BITRATES } from "@/components/BitrateSelector";
|
||||||
import {
|
import {
|
||||||
@@ -47,6 +48,7 @@ const DropdownView = ({
|
|||||||
const { settings, updateSettings } = useSettings();
|
const { settings, updateSettings } = useSettings();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isOffline = useOfflineMode();
|
const isOffline = useOfflineMode();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { subtitleIndex, audioIndex, bitrateValue, playbackPosition } =
|
const { subtitleIndex, audioIndex, bitrateValue, playbackPosition } =
|
||||||
useLocalSearchParams<{
|
useLocalSearchParams<{
|
||||||
@@ -101,7 +103,7 @@ const DropdownView = ({
|
|||||||
// Quality Section
|
// Quality Section
|
||||||
if (!isOffline) {
|
if (!isOffline) {
|
||||||
groups.push({
|
groups.push({
|
||||||
title: "Quality",
|
title: t("player.menu.quality"),
|
||||||
options:
|
options:
|
||||||
BITRATES?.map((bitrate) => ({
|
BITRATES?.map((bitrate) => ({
|
||||||
type: "radio" as const,
|
type: "radio" as const,
|
||||||
@@ -116,7 +118,7 @@ const DropdownView = ({
|
|||||||
// Subtitle Section
|
// Subtitle Section
|
||||||
if (subtitleTracks && subtitleTracks.length > 0) {
|
if (subtitleTracks && subtitleTracks.length > 0) {
|
||||||
groups.push({
|
groups.push({
|
||||||
title: "Subtitles",
|
title: t("player.menu.subtitles"),
|
||||||
options: subtitleTracks.map((sub) => ({
|
options: subtitleTracks.map((sub) => ({
|
||||||
type: "radio" as const,
|
type: "radio" as const,
|
||||||
label: sub.name,
|
label: sub.name,
|
||||||
@@ -128,7 +130,7 @@ const DropdownView = ({
|
|||||||
|
|
||||||
// Subtitle Scale Section
|
// Subtitle Scale Section
|
||||||
groups.push({
|
groups.push({
|
||||||
title: "Subtitle Scale",
|
title: t("player.menu.subtitle_scale"),
|
||||||
options: SUBTITLE_SCALE_PRESETS.map((preset) => ({
|
options: SUBTITLE_SCALE_PRESETS.map((preset) => ({
|
||||||
type: "radio" as const,
|
type: "radio" as const,
|
||||||
label: preset.label,
|
label: preset.label,
|
||||||
@@ -142,7 +144,7 @@ const DropdownView = ({
|
|||||||
// Audio Section
|
// Audio Section
|
||||||
if (audioTracks && audioTracks.length > 0) {
|
if (audioTracks && audioTracks.length > 0) {
|
||||||
groups.push({
|
groups.push({
|
||||||
title: "Audio",
|
title: t("player.menu.audio"),
|
||||||
options: audioTracks.map((track) => ({
|
options: audioTracks.map((track) => ({
|
||||||
type: "radio" as const,
|
type: "radio" as const,
|
||||||
label: track.name,
|
label: track.name,
|
||||||
@@ -156,7 +158,7 @@ const DropdownView = ({
|
|||||||
// Speed Section
|
// Speed Section
|
||||||
if (setPlaybackSpeed) {
|
if (setPlaybackSpeed) {
|
||||||
groups.push({
|
groups.push({
|
||||||
title: "Speed",
|
title: t("player.menu.speed"),
|
||||||
options: PLAYBACK_SPEEDS.map((speed) => ({
|
options: PLAYBACK_SPEEDS.map((speed) => ({
|
||||||
type: "radio" as const,
|
type: "radio" as const,
|
||||||
label: speed.label,
|
label: speed.label,
|
||||||
@@ -174,8 +176,8 @@ const DropdownView = ({
|
|||||||
{
|
{
|
||||||
type: "action" as const,
|
type: "action" as const,
|
||||||
label: showTechnicalInfo
|
label: showTechnicalInfo
|
||||||
? "Hide Technical Info"
|
? t("player.menu.hide_technical_info")
|
||||||
: "Show Technical Info",
|
: t("player.menu.show_technical_info"),
|
||||||
onPress: onToggleTechnicalInfo,
|
onPress: onToggleTechnicalInfo,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -185,6 +187,7 @@ const DropdownView = ({
|
|||||||
return groups;
|
return groups;
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [
|
}, [
|
||||||
|
t,
|
||||||
isOffline,
|
isOffline,
|
||||||
bitrateValue,
|
bitrateValue,
|
||||||
changeBitrate,
|
changeBitrate,
|
||||||
@@ -217,7 +220,7 @@ const DropdownView = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PlatformDropdown
|
<PlatformDropdown
|
||||||
title='Playback Options'
|
title={t("player.menu.playback_options")}
|
||||||
groups={optionGroups}
|
groups={optionGroups}
|
||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
expoUIConfig={{}}
|
expoUIConfig={{}}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Alert } from "react-native";
|
|||||||
import { type SharedValue, useSharedValue } from "react-native-reanimated";
|
import { type SharedValue, useSharedValue } from "react-native-reanimated";
|
||||||
import { useTVBackPress } from "@/hooks/useTVBackPress";
|
import { useTVBackPress } from "@/hooks/useTVBackPress";
|
||||||
import { useTVEventHandler } from "@/hooks/useTVEventHandler";
|
import { useTVEventHandler } from "@/hooks/useTVEventHandler";
|
||||||
|
import i18n from "@/i18n";
|
||||||
|
|
||||||
interface UseRemoteControlProps {
|
interface UseRemoteControlProps {
|
||||||
showControls: boolean;
|
showControls: boolean;
|
||||||
@@ -124,17 +125,23 @@ export function useRemoteControl({
|
|||||||
|
|
||||||
// Controls are hidden, so confirm before leaving playback.
|
// Controls are hidden, so confirm before leaving playback.
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
"Stop Playback",
|
i18n.t("player.stopPlayback"),
|
||||||
videoTitleRef.current
|
videoTitleRef.current
|
||||||
? `Stop playing "${videoTitleRef.current}"?`
|
? i18n.t("player.stopPlayingTitle", {
|
||||||
: "Are you sure you want to stop playback?",
|
title: videoTitleRef.current,
|
||||||
|
})
|
||||||
|
: i18n.t("player.stopPlayingConfirm"),
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
text: "Cancel",
|
text: i18n.t("common.cancel"),
|
||||||
style: "cancel",
|
style: "cancel",
|
||||||
onPress: () => onCancelExitRef.current?.(),
|
onPress: () => onCancelExitRef.current?.(),
|
||||||
},
|
},
|
||||||
{ text: "Stop", style: "destructive", onPress: onBackRef.current },
|
{
|
||||||
|
text: i18n.t("common.stop"),
|
||||||
|
style: "destructive",
|
||||||
|
onPress: onBackRef.current,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export class JellyseerrApi {
|
|||||||
if (inRange(status, 200, 299)) {
|
if (inRange(status, 200, 299)) {
|
||||||
if (data.version < "2.0.0") {
|
if (data.version < "2.0.0") {
|
||||||
const error = t(
|
const error = t(
|
||||||
"jellyseerr.toasts.jellyseer_does_not_meet_requirements",
|
"jellyseerr.toasts.jellyseerr_does_not_meet_requirements",
|
||||||
);
|
);
|
||||||
toast.error(error);
|
toast.error(error);
|
||||||
throw Error(error);
|
throw Error(error);
|
||||||
|
|||||||
@@ -1,125 +1,5 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
export {
|
||||||
import { Platform } from "react-native";
|
type PermissionStatus,
|
||||||
import { getSSID } from "@/modules/wifi-ssid";
|
type UseWifiSSIDReturn,
|
||||||
|
useWifiSsid as useWifiSSID,
|
||||||
export type PermissionStatus =
|
} from "@/providers/WifiSsidProvider";
|
||||||
| "granted"
|
|
||||||
| "denied"
|
|
||||||
| "undetermined"
|
|
||||||
| "unavailable";
|
|
||||||
|
|
||||||
export interface UseWifiSSIDReturn {
|
|
||||||
ssid: string | null;
|
|
||||||
permissionStatus: PermissionStatus;
|
|
||||||
requestPermission: () => Promise<boolean>;
|
|
||||||
isLoading: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// WiFi SSID is not available on tvOS
|
|
||||||
if (Platform.isTV) {
|
|
||||||
// Export a stub hook for tvOS
|
|
||||||
module.exports = {
|
|
||||||
useWifiSSID: (): UseWifiSSIDReturn => ({
|
|
||||||
ssid: null,
|
|
||||||
permissionStatus: "unavailable" as PermissionStatus,
|
|
||||||
requestPermission: async () => false,
|
|
||||||
isLoading: false,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only import Location on non-TV platforms
|
|
||||||
const Location = Platform.isTV ? null : require("expo-location");
|
|
||||||
|
|
||||||
function mapLocationStatus(status: number | undefined): PermissionStatus {
|
|
||||||
if (!Location) return "unavailable";
|
|
||||||
switch (status) {
|
|
||||||
case Location.PermissionStatus?.GRANTED:
|
|
||||||
return "granted";
|
|
||||||
case Location.PermissionStatus?.DENIED:
|
|
||||||
return "denied";
|
|
||||||
default:
|
|
||||||
return "undetermined";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useWifiSSID(): UseWifiSSIDReturn {
|
|
||||||
const [ssid, setSSID] = useState<string | null>(null);
|
|
||||||
const [permissionStatus, setPermissionStatus] = useState<PermissionStatus>(
|
|
||||||
Platform.isTV ? "unavailable" : "undetermined",
|
|
||||||
);
|
|
||||||
const [isLoading, setIsLoading] = useState(!Platform.isTV);
|
|
||||||
|
|
||||||
const fetchSSID = useCallback(async () => {
|
|
||||||
if (Platform.isTV) return;
|
|
||||||
const result = await getSSID();
|
|
||||||
setSSID(result);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const requestPermission = useCallback(async (): Promise<boolean> => {
|
|
||||||
if (Platform.isTV || !Location) {
|
|
||||||
setPermissionStatus("unavailable");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { status } = await Location.requestForegroundPermissionsAsync();
|
|
||||||
const newStatus = mapLocationStatus(status);
|
|
||||||
setPermissionStatus(newStatus);
|
|
||||||
|
|
||||||
if (newStatus === "granted") {
|
|
||||||
await fetchSSID();
|
|
||||||
}
|
|
||||||
|
|
||||||
return newStatus === "granted";
|
|
||||||
} catch {
|
|
||||||
setPermissionStatus("unavailable");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}, [fetchSSID]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (Platform.isTV || !Location) {
|
|
||||||
setIsLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initialize() {
|
|
||||||
setIsLoading(true);
|
|
||||||
try {
|
|
||||||
const { status } = await Location.getForegroundPermissionsAsync();
|
|
||||||
const mappedStatus = mapLocationStatus(status);
|
|
||||||
setPermissionStatus(mappedStatus);
|
|
||||||
|
|
||||||
if (mappedStatus === "granted") {
|
|
||||||
await fetchSSID();
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
setPermissionStatus("unavailable");
|
|
||||||
}
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize();
|
|
||||||
}, [fetchSSID]);
|
|
||||||
|
|
||||||
// Refresh SSID when permission status changes to granted
|
|
||||||
useEffect(() => {
|
|
||||||
if (Platform.isTV) return;
|
|
||||||
|
|
||||||
if (permissionStatus === "granted") {
|
|
||||||
fetchSSID();
|
|
||||||
|
|
||||||
// Also set up an interval to periodically check SSID
|
|
||||||
const interval = setInterval(fetchSSID, 10000); // Check every 10 seconds
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}
|
|
||||||
}, [permissionStatus, fetchSSID]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
ssid,
|
|
||||||
permissionStatus,
|
|
||||||
requestPermission,
|
|
||||||
isLoading,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
19
modules/wifi-ssid/android/build.gradle
Normal file
19
modules/wifi-ssid/android/build.gradle
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
apply plugin: 'expo-module-gradle-plugin'
|
||||||
|
|
||||||
|
group = 'expo.modules.wifissid'
|
||||||
|
version = '1.0.0'
|
||||||
|
|
||||||
|
expoModule {
|
||||||
|
canBePublished false
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace "expo.modules.wifissid"
|
||||||
|
defaultConfig {
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
}
|
||||||
9
modules/wifi-ssid/android/src/main/AndroidManifest.xml
Normal file
9
modules/wifi-ssid/android/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- Reading the connected Wi-Fi SSID requires ACCESS_WIFI_STATE, and (since
|
||||||
|
Android 8) ACCESS_FINE_LOCATION granted at runtime. Location is requested
|
||||||
|
at runtime by the app via expo-location; the manifest declares the
|
||||||
|
Wi-Fi/network permissions this module needs directly. -->
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package expo.modules.wifissid
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
import android.net.wifi.WifiInfo
|
||||||
|
import android.net.wifi.WifiManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.util.Log
|
||||||
|
import expo.modules.kotlin.modules.Module
|
||||||
|
import expo.modules.kotlin.modules.ModuleDefinition
|
||||||
|
|
||||||
|
class WifiSsidModule : Module() {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "WifiSsid"
|
||||||
|
private const val UNKNOWN_SSID = "<unknown ssid>"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val context
|
||||||
|
get() = requireNotNull(appContext.reactContext)
|
||||||
|
|
||||||
|
override fun definition() = ModuleDefinition {
|
||||||
|
Name("WifiSsid")
|
||||||
|
|
||||||
|
AsyncFunction("getSSID") {
|
||||||
|
val (ssid, connectedToWifi) = readWifi()
|
||||||
|
mapOf(
|
||||||
|
"ssid" to ssid,
|
||||||
|
"connectedToWifi" to connectedToWifi,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Function("getSSIDSync") {
|
||||||
|
val (ssid, connectedToWifi) = readWifi()
|
||||||
|
mapOf(
|
||||||
|
"ssid" to ssid,
|
||||||
|
"connectedToWifi" to connectedToWifi,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Function("openLocationSettings") {
|
||||||
|
openLocationSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current Wi-Fi SSID (or null when it can't be read) and whether
|
||||||
|
* the device is connected to a Wi-Fi network. When the device is connected but
|
||||||
|
* the SSID is null, the OS is withholding the network name — on Android this
|
||||||
|
* typically means device Location services are off (the SSID is treated as
|
||||||
|
* location-sensitive).
|
||||||
|
*/
|
||||||
|
private fun readWifi(): Pair<String?, Boolean> {
|
||||||
|
val androidContext = context.applicationContext
|
||||||
|
var connectedToWifi = false
|
||||||
|
var ssid: String? = null
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
try {
|
||||||
|
val cm = androidContext.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
|
||||||
|
val caps = cm?.let { manager ->
|
||||||
|
manager.activeNetwork?.let { net -> manager.getNetworkCapabilities(net) }
|
||||||
|
}
|
||||||
|
connectedToWifi = caps?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
|
||||||
|
if (hasFineLocationPermission()) {
|
||||||
|
ssid = (caps?.transportInfo as? WifiInfo)?.ssid?.sanitize()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error reading Wi-Fi via ConnectivityManager", e)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Pre-Q (API 26–28): NetworkCapabilities/transportInfo aren't available, so
|
||||||
|
// detect a Wi-Fi connection via the legacy active-network API. Without this,
|
||||||
|
// connectedToWifi stays false on Android 8/9 even when connected, so the
|
||||||
|
// "connected but SSID hidden" state never surfaces there.
|
||||||
|
try {
|
||||||
|
val cm = androidContext.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
connectedToWifi = cm?.activeNetworkInfo?.type == ConnectivityManager.TYPE_WIFI
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error reading Wi-Fi via ConnectivityManager (legacy)", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ssid == null && hasFineLocationPermission()) {
|
||||||
|
try {
|
||||||
|
val wm = androidContext.getSystemService(Context.WIFI_SERVICE) as? WifiManager
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
ssid = wm?.connectionInfo?.ssid?.sanitize()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error reading SSID via WifiManager", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pair(ssid, connectedToWifi)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openLocationSettings() {
|
||||||
|
try {
|
||||||
|
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS).apply {
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}
|
||||||
|
context.applicationContext.startActivity(intent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Unable to open Location settings", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hasFineLocationPermission(): Boolean =
|
||||||
|
context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) ==
|
||||||
|
PackageManager.PERMISSION_GRANTED
|
||||||
|
|
||||||
|
private fun String.sanitize(): String? {
|
||||||
|
val cleaned = trim('"')
|
||||||
|
return if (cleaned.isBlank() || cleaned == UNKNOWN_SSID) null else cleaned
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "wifi-ssid",
|
"name": "wifi-ssid",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"platforms": ["ios"],
|
"platforms": ["ios", "android"],
|
||||||
"ios": {
|
"ios": {
|
||||||
"modules": ["WifiSsidModule"]
|
"modules": ["WifiSsidModule"]
|
||||||
|
},
|
||||||
|
"android": {
|
||||||
|
"modules": ["expo.modules.wifissid.WifiSsidModule"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,92 @@
|
|||||||
import { Platform, requireNativeModule } from "expo-modules-core";
|
import { Platform, requireNativeModule } from "expo-modules-core";
|
||||||
|
import { Linking } from "react-native";
|
||||||
|
|
||||||
// Only load the native module on iOS
|
export interface WifiSsidResult {
|
||||||
const WifiSsidModule =
|
ssid: string | null;
|
||||||
Platform.OS === "ios" ? requireNativeModule("WifiSsid") : null;
|
/**
|
||||||
|
* Whether the device is connected to a Wi-Fi network. When true but `ssid` is
|
||||||
|
* null, the OS is withholding the network name — on Android this happens when
|
||||||
|
* device Location services are off (the SSID is location-sensitive).
|
||||||
|
*/
|
||||||
|
connectedToWifi: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the native module on iOS and Android (not TV/web). Wrapped so a
|
||||||
|
// missing/unlinked module degrades to null instead of crashing the bundle.
|
||||||
|
const WifiSsidModule = (() => {
|
||||||
|
if (Platform.OS !== "ios" && Platform.OS !== "android") return null;
|
||||||
|
try {
|
||||||
|
return requireNativeModule("WifiSsid");
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current WiFi SSID on iOS.
|
* Get the current WiFi SSID and whether the device is on a Wi-Fi network.
|
||||||
* Returns null on Android or if not connected to WiFi.
|
|
||||||
*
|
*
|
||||||
* Requires:
|
* iOS requirements:
|
||||||
* - Location permission granted
|
* - Location permission granted
|
||||||
* - com.apple.developer.networking.wifi-info entitlement
|
* - com.apple.developer.networking.wifi-info entitlement
|
||||||
* - Access WiFi Information capability enabled in Apple Developer Portal
|
*
|
||||||
|
* Android requirements:
|
||||||
|
* - ACCESS_FINE_LOCATION granted at runtime, and device Location services on
|
||||||
*/
|
*/
|
||||||
export async function getSSID(): Promise<string | null> {
|
export async function getSSID(): Promise<WifiSsidResult> {
|
||||||
if (!WifiSsidModule) {
|
if (!WifiSsidModule) {
|
||||||
return null;
|
return { ssid: null, connectedToWifi: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ssid = await WifiSsidModule.getSSID();
|
return normalize(await WifiSsidModule.getSSID());
|
||||||
return ssid ?? null;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[WifiSsid] Error getting SSID:", error);
|
console.error("[WifiSsid] Error getting SSID:", error);
|
||||||
return null;
|
return { ssid: null, connectedToWifi: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Synchronous version (uses the older CNCopyCurrentNetworkInfo API on iOS). */
|
||||||
|
export function getSSIDSync(): WifiSsidResult {
|
||||||
|
if (!WifiSsidModule) {
|
||||||
|
return { ssid: null, connectedToWifi: false };
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return normalize(WifiSsidModule.getSSIDSync());
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[WifiSsid] Error getting SSID (sync):", error);
|
||||||
|
return { ssid: null, connectedToWifi: false };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronous version - uses older CNCopyCurrentNetworkInfo API
|
* Open the system Location settings so the user can enable Location services
|
||||||
|
* (required on Android for the OS to reveal the Wi-Fi SSID). Falls back to the
|
||||||
|
* app's settings page where there is no dedicated native action (e.g. iOS).
|
||||||
*/
|
*/
|
||||||
export function getSSIDSync(): string | null {
|
export function openLocationSettings(): void {
|
||||||
if (!WifiSsidModule) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return WifiSsidModule.getSSIDSync() ?? null;
|
if (WifiSsidModule?.openLocationSettings) {
|
||||||
|
WifiSsidModule.openLocationSettings();
|
||||||
|
return;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[WifiSsid] Error getting SSID (sync):", error);
|
console.error("[WifiSsid] Error opening location settings:", error);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
Linking.openSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both native modules return { ssid, connectedToWifi }. The string branch is a
|
||||||
|
// defensive fallback in case a build returns the older plain-string shape.
|
||||||
|
function normalize(result: unknown): WifiSsidResult {
|
||||||
|
if (typeof result === "string") {
|
||||||
|
const ssid = result || null;
|
||||||
|
return { ssid, connectedToWifi: ssid !== null };
|
||||||
|
}
|
||||||
|
if (result && typeof result === "object") {
|
||||||
|
const obj = result as { ssid?: string | null; connectedToWifi?: boolean };
|
||||||
|
return {
|
||||||
|
ssid: obj.ssid ?? null,
|
||||||
|
connectedToWifi: Boolean(obj.connectedToWifi),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { ssid: null, connectedToWifi: false };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,113 @@
|
|||||||
import ExpoModulesCore
|
import ExpoModulesCore
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
|
import Network
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
import SystemConfiguration.CaptiveNetwork
|
import SystemConfiguration.CaptiveNetwork
|
||||||
#endif
|
#endif
|
||||||
|
import UIKit
|
||||||
|
|
||||||
public class WifiSsidModule: Module {
|
public class WifiSsidModule: Module {
|
||||||
|
#if !os(tvOS)
|
||||||
|
// NWPathMonitor reports whether the active path uses Wi-Fi WITHOUT requiring
|
||||||
|
// the wifi-info entitlement, so we can tell "on Wi-Fi but can't read the SSID"
|
||||||
|
// apart from "not connected to Wi-Fi" even when fetchCurrent returns nil.
|
||||||
|
private let pathMonitor = NWPathMonitor()
|
||||||
|
private let monitorQueue = DispatchQueue(label: "expo.modules.wifissid.pathmonitor")
|
||||||
|
private let lock = NSLock()
|
||||||
|
private var cachedOnWifi = false
|
||||||
|
private var pathMonitorStarted = false
|
||||||
|
|
||||||
|
private var isOnWifi: Bool {
|
||||||
|
lock.lock()
|
||||||
|
defer { lock.unlock() }
|
||||||
|
return cachedOnWifi
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startPathMonitorIfNeeded() {
|
||||||
|
lock.lock()
|
||||||
|
if pathMonitorStarted {
|
||||||
|
lock.unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pathMonitorStarted = true
|
||||||
|
lock.unlock()
|
||||||
|
|
||||||
|
pathMonitor.pathUpdateHandler = { [weak self] path in
|
||||||
|
guard let self else { return }
|
||||||
|
let onWifi = path.status == .satisfied && path.usesInterfaceType(.wifi)
|
||||||
|
self.lock.lock()
|
||||||
|
self.cachedOnWifi = onWifi
|
||||||
|
self.lock.unlock()
|
||||||
|
}
|
||||||
|
pathMonitor.start(queue: monitorQueue)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
pathMonitor.cancel()
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
public func definition() -> ModuleDefinition {
|
public func definition() -> ModuleDefinition {
|
||||||
Name("WifiSsid")
|
Name("WifiSsid")
|
||||||
|
|
||||||
// Get current WiFi SSID using NEHotspotNetwork (iOS 14+)
|
AsyncFunction("getSSID") { () -> [String: Any] in
|
||||||
// Not available on tvOS
|
|
||||||
AsyncFunction("getSSID") { () -> String? in
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
return nil
|
return ["ssid": NSNull(), "connectedToWifi": false]
|
||||||
#else
|
#else
|
||||||
return await withCheckedContinuation { continuation in
|
self.startPathMonitorIfNeeded()
|
||||||
NEHotspotNetwork.fetchCurrent { network in
|
let ssid = await self.resolveSSID()
|
||||||
if let ssid = network?.ssid {
|
var result: [String: Any] = ["connectedToWifi": self.isOnWifi]
|
||||||
print("[WifiSsid] Got SSID via NEHotspotNetwork: \(ssid)")
|
if let ssid = ssid {
|
||||||
continuation.resume(returning: ssid)
|
result["ssid"] = ssid
|
||||||
} else {
|
} else {
|
||||||
// Fallback to CNCopyCurrentNetworkInfo for older iOS
|
result["ssid"] = NSNull()
|
||||||
print("[WifiSsid] NEHotspotNetwork returned nil, trying CNCopyCurrentNetworkInfo")
|
|
||||||
let ssid = self.getSSIDViaCNCopy()
|
|
||||||
continuation.resume(returning: ssid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synchronous version using only CNCopyCurrentNetworkInfo
|
Function("getSSIDSync") { () -> [String: Any] in
|
||||||
// Not available on tvOS
|
|
||||||
Function("getSSIDSync") { () -> String? in
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
return nil
|
return ["ssid": NSNull(), "connectedToWifi": false]
|
||||||
#else
|
#else
|
||||||
return self.getSSIDViaCNCopy()
|
self.startPathMonitorIfNeeded()
|
||||||
|
let ssid = self.getSSIDViaCNCopy()
|
||||||
|
var result: [String: Any] = ["connectedToWifi": self.isOnWifi]
|
||||||
|
if let ssid = ssid {
|
||||||
|
result["ssid"] = ssid
|
||||||
|
} else {
|
||||||
|
result["ssid"] = NSNull()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
Function("openLocationSettings") {
|
||||||
|
#if !os(tvOS)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if let url = URL(string: UIApplication.openSettingsURLString) {
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
|
private func resolveSSID() async -> String? {
|
||||||
|
await withCheckedContinuation { continuation in
|
||||||
|
NEHotspotNetwork.fetchCurrent { network in
|
||||||
|
if let ssid = network?.ssid {
|
||||||
|
continuation.resume(returning: ssid)
|
||||||
|
} else {
|
||||||
|
continuation.resume(returning: self.getSSIDViaCNCopy())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func getSSIDViaCNCopy() -> String? {
|
private func getSSIDViaCNCopy() -> String? {
|
||||||
guard let interfaces = CNCopySupportedInterfaces() as? [String] else {
|
guard let interfaces = CNCopySupportedInterfaces() as? [String] else {
|
||||||
print("[WifiSsid] CNCopySupportedInterfaces returned nil")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,12 +117,10 @@ public class WifiSsidModule: Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let ssid = networkInfo[kCNNetworkInfoKeySSID as String] as? String {
|
if let ssid = networkInfo[kCNNetworkInfoKeySSID as String] as? String {
|
||||||
print("[WifiSsid] Got SSID via CNCopyCurrentNetworkInfo: \(ssid)")
|
|
||||||
return ssid
|
return ssid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print("[WifiSsid] No SSID found via CNCopyCurrentNetworkInfo")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
"postinstall": "patch-package"
|
"postinstall": "patch-package"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bottom-tabs/react-navigation": "1.2.0",
|
"@bottom-tabs/react-navigation": "1.4.0",
|
||||||
"@douglowder/expo-av-route-picker-view": "^0.0.5",
|
"@douglowder/expo-av-route-picker-view": "^0.0.5",
|
||||||
"@expo/metro-runtime": "~56.0.15",
|
"@expo/metro-runtime": "~56.0.15",
|
||||||
"@expo/react-native-action-sheet": "^4.1.1",
|
"@expo/react-native-action-sheet": "^4.1.1",
|
||||||
@@ -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",
|
||||||
@@ -61,6 +62,7 @@
|
|||||||
"expo-font": "~56.0.6",
|
"expo-font": "~56.0.6",
|
||||||
"expo-haptics": "~56.0.3",
|
"expo-haptics": "~56.0.3",
|
||||||
"expo-image": "~56.0.11",
|
"expo-image": "~56.0.11",
|
||||||
|
"expo-key-event": "^1.9.0",
|
||||||
"expo-linear-gradient": "~56.0.4",
|
"expo-linear-gradient": "~56.0.4",
|
||||||
"expo-linking": "~56.0.14",
|
"expo-linking": "~56.0.14",
|
||||||
"expo-localization": "~56.0.6",
|
"expo-localization": "~56.0.6",
|
||||||
@@ -85,7 +87,7 @@
|
|||||||
"react-i18next": "17.0.8",
|
"react-i18next": "17.0.8",
|
||||||
"react-native": "npm:react-native-tvos@0.85.3-0",
|
"react-native": "npm:react-native-tvos@0.85.3-0",
|
||||||
"react-native-awesome-slider": "^2.9.0",
|
"react-native-awesome-slider": "^2.9.0",
|
||||||
"react-native-bottom-tabs": "1.2.0",
|
"react-native-bottom-tabs": "1.4.0",
|
||||||
"react-native-circular-progress": "^1.4.1",
|
"react-native-circular-progress": "^1.4.1",
|
||||||
"react-native-collapsible": "^1.6.2",
|
"react-native-collapsible": "^1.6.2",
|
||||||
"react-native-country-flag": "^2.0.2",
|
"react-native-country-flag": "^2.0.2",
|
||||||
@@ -115,7 +117,7 @@
|
|||||||
"react-native-uuid": "^2.0.3",
|
"react-native-uuid": "^2.0.3",
|
||||||
"react-native-volume-manager": "^2.0.8",
|
"react-native-volume-manager": "^2.0.8",
|
||||||
"react-native-web": "^0.21.0",
|
"react-native-web": "^0.21.0",
|
||||||
"react-native-worklets": "0.11.0",
|
"react-native-worklets": "0.8.3",
|
||||||
"sonner-native": "0.21.2",
|
"sonner-native": "0.21.2",
|
||||||
"tailwindcss": "3.3.2",
|
"tailwindcss": "3.3.2",
|
||||||
"use-debounce": "^10.0.4",
|
"use-debounce": "^10.0.4",
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
diff --git a/node_modules/react-native-bottom-tabs/ios/BottomAccessoryProvider.swift b/node_modules/react-native-bottom-tabs/ios/BottomAccessoryProvider.swift
|
|
||||||
index 539efee7156599e1fc795e11bf411b7dfaf12ec7..b2af39a2e6b014e9b1ae0a51b21115c19280df69 100644
|
|
||||||
--- a/node_modules/react-native-bottom-tabs/ios/BottomAccessoryProvider.swift
|
|
||||||
+++ b/node_modules/react-native-bottom-tabs/ios/BottomAccessoryProvider.swift
|
|
||||||
@@ -8,7 +8,7 @@ import SwiftUI
|
|
||||||
self.delegate = delegate
|
|
||||||
}
|
|
||||||
|
|
||||||
- #if !os(macOS)
|
|
||||||
+ #if !os(macOS) && !os(tvOS)
|
|
||||||
@available(iOS 26.0, *)
|
|
||||||
public func emitPlacementChanged(_ placement: TabViewBottomAccessoryPlacement?) {
|
|
||||||
var placementValue = "none"
|
|
||||||
diff --git a/node_modules/react-native-bottom-tabs/ios/TabView/NewTabView.swift b/node_modules/react-native-bottom-tabs/ios/TabView/NewTabView.swift
|
|
||||||
index 22c52cdf25ad0f7398d89197cb431ca8dc8e0f99..81411376e68803de8bd83515d42565cfa95daf2b 100644
|
|
||||||
--- a/node_modules/react-native-bottom-tabs/ios/TabView/NewTabView.swift
|
|
||||||
+++ b/node_modules/react-native-bottom-tabs/ios/TabView/NewTabView.swift
|
|
||||||
@@ -78,11 +78,11 @@ struct ConditionalBottomAccessoryModifier: ViewModifier {
|
|
||||||
}
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
|
||||||
- #if os(macOS)
|
|
||||||
- // tabViewBottomAccessory is not available on macOS
|
|
||||||
+ #if os(macOS) || os(tvOS)
|
|
||||||
+ // tabViewBottomAccessory is not available on macOS or tvOS
|
|
||||||
content
|
|
||||||
#else
|
|
||||||
- if #available(iOS 26.0, tvOS 26.0, visionOS 3.0, *), bottomAccessoryView != nil {
|
|
||||||
+ if #available(iOS 26.0, visionOS 3.0, *), bottomAccessoryView != nil {
|
|
||||||
content
|
|
||||||
.tabViewBottomAccessory {
|
|
||||||
renderBottomAccessoryView()
|
|
||||||
@@ -95,7 +95,7 @@ struct ConditionalBottomAccessoryModifier: ViewModifier {
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private func renderBottomAccessoryView() -> some View {
|
|
||||||
- #if !os(macOS)
|
|
||||||
+ #if !os(macOS) && !os(tvOS)
|
|
||||||
if let bottomAccessoryView {
|
|
||||||
if #available(iOS 26.0, *) {
|
|
||||||
BottomAccessoryRepresentableView(view: bottomAccessoryView)
|
|
||||||
@@ -105,7 +105,7 @@ struct ConditionalBottomAccessoryModifier: ViewModifier {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-#if !os(macOS)
|
|
||||||
+#if !os(macOS) && !os(tvOS)
|
|
||||||
@available(iOS 26.0, *)
|
|
||||||
struct BottomAccessoryRepresentableView: PlatformViewRepresentable {
|
|
||||||
@Environment(\.tabViewBottomAccessoryPlacement) var tabViewBottomAccessoryPlacement
|
|
||||||
@@ -135,3 +135,4 @@ struct BottomAccessoryRepresentableView: PlatformViewRepresentable {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
+
|
|
||||||
diff --git a/node_modules/react-native-bottom-tabs/ios/TabViewImpl.swift b/node_modules/react-native-bottom-tabs/ios/TabViewImpl.swift
|
|
||||||
index 72938be90540ea3a483d7db9a80fb74c04d31272..277278ffdd9268a96cb09869eb1d0c0d5e6ad300 100644
|
|
||||||
--- a/node_modules/react-native-bottom-tabs/ios/TabViewImpl.swift
|
|
||||||
+++ b/node_modules/react-native-bottom-tabs/ios/TabViewImpl.swift
|
|
||||||
@@ -281,7 +281,7 @@ extension View {
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
func tabBarMinimizeBehavior(_ behavior: MinimizeBehavior?) -> some View {
|
|
||||||
- #if compiler(>=6.2)
|
|
||||||
+ #if compiler(>=6.2) && !os(tvOS)
|
|
||||||
if #available(iOS 26.0, macOS 26.0, *) {
|
|
||||||
if let behavior {
|
|
||||||
self.tabBarMinimizeBehavior(behavior.convert())
|
|
||||||
diff --git a/node_modules/react-native-bottom-tabs/ios/TabViewProps.swift b/node_modules/react-native-bottom-tabs/ios/TabViewProps.swift
|
|
||||||
index 9cfb29a983b34d3f84fc7a678d19ef4ff30e0325..6a5854483e66200b71722bbac12e100742222bd3 100644
|
|
||||||
--- a/node_modules/react-native-bottom-tabs/ios/TabViewProps.swift
|
|
||||||
+++ b/node_modules/react-native-bottom-tabs/ios/TabViewProps.swift
|
|
||||||
@@ -6,7 +6,7 @@ internal enum MinimizeBehavior: String {
|
|
||||||
case onScrollUp
|
|
||||||
case onScrollDown
|
|
||||||
|
|
||||||
-#if compiler(>=6.2)
|
|
||||||
+#if compiler(>=6.2) && !os(tvOS)
|
|
||||||
@available(iOS 26.0, macOS 26.0, *)
|
|
||||||
func convert() -> TabBarMinimizeBehavior {
|
|
||||||
#if os(macOS)
|
|
||||||
@@ -15,12 +15,14 @@ import {
|
|||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { AppState, Platform } from "react-native";
|
import { AppState, Platform } from "react-native";
|
||||||
import { getDeviceNameSync } from "react-native-device-info";
|
import { getDeviceNameSync } from "react-native-device-info";
|
||||||
import uuid from "react-native-uuid";
|
import uuid from "react-native-uuid";
|
||||||
|
import { toast } from "sonner-native";
|
||||||
import useRouter from "@/hooks/useAppRouter";
|
import useRouter from "@/hooks/useAppRouter";
|
||||||
import { useInterval } from "@/hooks/useInterval";
|
import { useInterval } from "@/hooks/useInterval";
|
||||||
import { JellyseerrApi, useJellyseerr } from "@/hooks/useJellyseerr";
|
import { JellyseerrApi, useJellyseerr } from "@/hooks/useJellyseerr";
|
||||||
@@ -91,6 +93,12 @@ export const apiAtom = atom<Api | null>(initialApi);
|
|||||||
export const userAtom = atom<UserDto | null>(initialUser);
|
export const userAtom = atom<UserDto | null>(initialUser);
|
||||||
export const wsAtom = atom<WebSocket | null>(null);
|
export const wsAtom = atom<WebSocket | null>(null);
|
||||||
export const cacheVersionAtom = atom<number>(0);
|
export const cacheVersionAtom = atom<number>(0);
|
||||||
|
// Set by a login flow that wants the account saved: the protection picker
|
||||||
|
// shows AFTER the session is authorized (the login screen unmounts on
|
||||||
|
// success, so the modal lives at the root — see PendingAccountSaveModal).
|
||||||
|
export const pendingAccountSaveAtom = atom<{ serverName?: string } | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
interface LoginOptions {
|
interface LoginOptions {
|
||||||
saveAccount?: boolean;
|
saveAccount?: boolean;
|
||||||
@@ -108,6 +116,11 @@ interface JellyfinContextValue {
|
|||||||
serverName?: string,
|
serverName?: string,
|
||||||
options?: LoginOptions,
|
options?: LoginOptions,
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
saveCurrentAccount: (options?: {
|
||||||
|
securityType?: AccountSecurityType;
|
||||||
|
pinCode?: string;
|
||||||
|
serverName?: string;
|
||||||
|
}) => Promise<void>;
|
||||||
logout: () => Promise<void>;
|
logout: () => Promise<void>;
|
||||||
initiateQuickConnect: () => Promise<string | undefined>;
|
initiateQuickConnect: () => Promise<string | undefined>;
|
||||||
stopQuickConnectPolling: () => void;
|
stopQuickConnectPolling: () => void;
|
||||||
@@ -165,6 +178,69 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
const { clearAllJellyseerData, setJellyseerrUser } = useJellyseerr();
|
const { clearAllJellyseerData, setJellyseerrUser } = useJellyseerr();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
// --- Session-expiry handling ----------------------------------------------
|
||||||
|
// When the server revokes the token (e.g. the device/session is deleted), a
|
||||||
|
// 401 can surface from any authenticated request. Without central handling
|
||||||
|
// the dead token stays in storage, so every reload re-fires authed calls →
|
||||||
|
// 401 spam + uncaught rejections, and the app lingers in a half-authenticated
|
||||||
|
// state. A single response interceptor on the authenticated api clears the
|
||||||
|
// session on the first 401 so the app drops cleanly to the login screen.
|
||||||
|
const sessionExpiredRef = useRef(false);
|
||||||
|
|
||||||
|
// Shared teardown for manual logout AND forced session expiry — keeping it
|
||||||
|
// in one place prevents the two paths from drifting (a 401 expiry must wipe
|
||||||
|
// plugin settings / Jellyseerr state too, or the next login on the same
|
||||||
|
// device inherits the previous user's data).
|
||||||
|
// Saved credentials are kept so the user can quick-login again.
|
||||||
|
const clearSessionState = useCallback(async () => {
|
||||||
|
// All synchronous teardown first: if the async Jellyseerr cleanup below
|
||||||
|
// fails or resolves late (user may already be re-authenticating), the
|
||||||
|
// session/cache state is already gone.
|
||||||
|
storage.remove("token");
|
||||||
|
storage.remove("user");
|
||||||
|
storage.remove("REACT_QUERY_OFFLINE_CACHE");
|
||||||
|
clearTVDiscoverySafely();
|
||||||
|
setUser(null);
|
||||||
|
setApi(null);
|
||||||
|
setPluginSettings(undefined);
|
||||||
|
queryClient.clear();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await clearAllJellyseerData();
|
||||||
|
} catch (e) {
|
||||||
|
writeErrorLog(
|
||||||
|
`Failed to clear Jellyseerr data: ${e instanceof Error ? e.message : e}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [setUser, setApi, setPluginSettings, clearAllJellyseerData, queryClient]);
|
||||||
|
|
||||||
|
const handleSessionExpired = useCallback(() => {
|
||||||
|
if (sessionExpiredRef.current) return; // run once per session
|
||||||
|
sessionExpiredRef.current = true;
|
||||||
|
clearSessionState().catch((e) =>
|
||||||
|
writeErrorLog(`Session-expiry cleanup failed: ${e?.message ?? e}`),
|
||||||
|
);
|
||||||
|
}, [clearSessionState]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Only guard an authenticated session. A pre-auth api (login screen) keeps
|
||||||
|
// its own handling — a wrong-password 401 is not a session expiry.
|
||||||
|
if (!api?.accessToken) return;
|
||||||
|
sessionExpiredRef.current = false; // re-arm for this fresh session
|
||||||
|
const interceptorId = api.axiosInstance.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
(error) => {
|
||||||
|
if (error?.response?.status === 401) {
|
||||||
|
handleSessionExpired();
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return () => {
|
||||||
|
api.axiosInstance.interceptors.response.eject(interceptorId);
|
||||||
|
};
|
||||||
|
}, [api, handleSessionExpired]);
|
||||||
|
|
||||||
const headers = useMemo(() => {
|
const headers = useMemo(() => {
|
||||||
if (!deviceId) return {};
|
if (!deviceId) return {};
|
||||||
return {
|
return {
|
||||||
@@ -307,6 +383,40 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Persist the CURRENT session to secure storage — used by the post-login
|
||||||
|
// save-account modal (the protection picker shows AFTER a successful
|
||||||
|
// login, for both the password and Quick Connect flows).
|
||||||
|
const saveCurrentAccount = useCallback(
|
||||||
|
async (options?: {
|
||||||
|
securityType?: AccountSecurityType;
|
||||||
|
pinCode?: string;
|
||||||
|
serverName?: string;
|
||||||
|
}) => {
|
||||||
|
const token = storage.getString("token");
|
||||||
|
if (!api?.basePath || !user?.Id || !user.Name || !token) return;
|
||||||
|
const securityType = options?.securityType || "none";
|
||||||
|
let pinHash: string | undefined;
|
||||||
|
if (securityType === "pin") {
|
||||||
|
// Never persist a "pin" credential without its hash — it would be
|
||||||
|
// impossible to unlock.
|
||||||
|
if (!options?.pinCode) throw new Error("PIN code is required");
|
||||||
|
pinHash = await hashPIN(options.pinCode);
|
||||||
|
}
|
||||||
|
await saveAccountCredential({
|
||||||
|
serverUrl: api.basePath,
|
||||||
|
serverName: options?.serverName || "",
|
||||||
|
token,
|
||||||
|
userId: user.Id,
|
||||||
|
username: user.Name,
|
||||||
|
savedAt: Date.now(),
|
||||||
|
securityType,
|
||||||
|
pinHash,
|
||||||
|
primaryImageTag: user.PrimaryImageTag ?? undefined,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[api?.basePath, user],
|
||||||
|
);
|
||||||
|
|
||||||
const loginMutation = useMutation({
|
const loginMutation = useMutation({
|
||||||
mutationFn: async ({
|
mutationFn: async ({
|
||||||
username,
|
username,
|
||||||
@@ -338,18 +448,25 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
if (securityType === "pin" && options.pinCode) {
|
if (securityType === "pin" && options.pinCode) {
|
||||||
pinHash = await hashPIN(options.pinCode);
|
pinHash = await hashPIN(options.pinCode);
|
||||||
}
|
}
|
||||||
|
if (securityType === "pin" && !pinHash) {
|
||||||
await saveAccountCredential({
|
// Never persist a "pin" credential without its hash — it would be
|
||||||
serverUrl: api.basePath,
|
// impossible to unlock. Skip the save rather than failing a login
|
||||||
serverName: serverName || "",
|
// that already succeeded, and tell the user it didn't happen.
|
||||||
token: auth.data.AccessToken,
|
writeErrorLog("Account save skipped: PIN required but missing");
|
||||||
userId: auth.data.User.Id || "",
|
toast.error(t("save_account.not_saved"));
|
||||||
username,
|
} else {
|
||||||
savedAt: Date.now(),
|
await saveAccountCredential({
|
||||||
securityType,
|
serverUrl: api.basePath,
|
||||||
pinHash,
|
serverName: serverName || "",
|
||||||
primaryImageTag: auth.data.User.PrimaryImageTag ?? undefined,
|
token: auth.data.AccessToken,
|
||||||
});
|
userId: auth.data.User.Id || "",
|
||||||
|
username,
|
||||||
|
savedAt: Date.now(),
|
||||||
|
securityType,
|
||||||
|
pinHash,
|
||||||
|
primaryImageTag: auth.data.User.PrimaryImageTag ?? undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const recentPluginSettings = await refreshStreamyfinPluginSettings();
|
const recentPluginSettings = await refreshStreamyfinPluginSettings();
|
||||||
@@ -409,19 +526,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
writeErrorLog("Failed to delete expo push token for device"),
|
writeErrorLog("Failed to delete expo push token for device"),
|
||||||
);
|
);
|
||||||
|
|
||||||
storage.remove("token");
|
await clearSessionState();
|
||||||
storage.remove("user");
|
|
||||||
clearTVDiscoverySafely();
|
|
||||||
setUser(null);
|
|
||||||
setApi(null);
|
|
||||||
setPluginSettings(undefined);
|
|
||||||
await clearAllJellyseerData();
|
|
||||||
|
|
||||||
// Clear React Query cache to prevent data from previous account lingering
|
|
||||||
queryClient.clear();
|
|
||||||
storage.remove("REACT_QUERY_OFFLINE_CACHE");
|
|
||||||
|
|
||||||
// Note: We keep saved credentials for quick switching back
|
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.error("Logout failed:", error);
|
console.error("Logout failed:", error);
|
||||||
@@ -509,7 +614,9 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.error("Quick login failed:", error);
|
// Expected, handled case (e.g. revoked token → "Session Expired", or
|
||||||
|
// server unreachable): the UI surfaces the message, so warn, don't error.
|
||||||
|
console.warn("Quick login failed:", error);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -620,54 +727,66 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
setUser(storedUser);
|
setUser(storedUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dismiss splash screen with cached data immediately,
|
// Validate the token and refresh user data in the background. Do NOT
|
||||||
// fetch fresh user data in the background
|
// await this: the Jellyfin SDK axios instance has no timeout, so when
|
||||||
setInitialLoaded(true);
|
// offline this call hangs for the full OS TCP timeout (75-120s) and
|
||||||
|
// blocks splash dismissal. The cached storedUser (set above) is enough
|
||||||
|
// to render; on success we just refresh it.
|
||||||
|
getUserApi(apiInstance)
|
||||||
|
.getCurrentUser()
|
||||||
|
.then(async (response) => {
|
||||||
|
// The response can resolve long after startup (no axios timeout).
|
||||||
|
// If the session changed meanwhile (logout, account switch), drop
|
||||||
|
// it instead of repopulating a stale user / re-saving credentials.
|
||||||
|
if (getTokenFromStorage() !== token) return;
|
||||||
|
setUser(response.data);
|
||||||
|
|
||||||
try {
|
// Migrate current session to secure storage if not already saved
|
||||||
const response = await getUserApi(apiInstance).getCurrentUser();
|
if (storedUser?.Id && storedUser?.Name) {
|
||||||
setUser(response.data);
|
const existingCredential = await getAccountCredential(
|
||||||
|
|
||||||
// Migrate current session to secure storage if not already saved
|
|
||||||
if (storedUser?.Id && storedUser?.Name) {
|
|
||||||
const existingCredential = await getAccountCredential(
|
|
||||||
serverUrl,
|
|
||||||
storedUser.Id,
|
|
||||||
);
|
|
||||||
if (!existingCredential) {
|
|
||||||
await saveAccountCredential({
|
|
||||||
serverUrl,
|
serverUrl,
|
||||||
serverName: "",
|
storedUser.Id,
|
||||||
token,
|
);
|
||||||
userId: storedUser.Id,
|
if (!existingCredential) {
|
||||||
username: storedUser.Name,
|
await saveAccountCredential({
|
||||||
savedAt: Date.now(),
|
serverUrl,
|
||||||
securityType: "none",
|
serverName: "",
|
||||||
primaryImageTag: response.data.PrimaryImageTag ?? undefined,
|
token,
|
||||||
});
|
userId: storedUser.Id,
|
||||||
} else if (
|
username: storedUser.Name,
|
||||||
response.data.PrimaryImageTag !==
|
savedAt: Date.now(),
|
||||||
existingCredential.primaryImageTag
|
securityType: "none",
|
||||||
) {
|
primaryImageTag: response.data.PrimaryImageTag ?? undefined,
|
||||||
// Update image tag if it has changed
|
});
|
||||||
addAccountToServer(serverUrl, existingCredential.serverName, {
|
} else if (
|
||||||
userId: existingCredential.userId,
|
response.data.PrimaryImageTag !==
|
||||||
username: existingCredential.username,
|
existingCredential.primaryImageTag
|
||||||
securityType: existingCredential.securityType,
|
) {
|
||||||
savedAt: existingCredential.savedAt,
|
// Update image tag if it has changed
|
||||||
primaryImageTag: response.data.PrimaryImageTag ?? undefined,
|
addAccountToServer(serverUrl, existingCredential.serverName, {
|
||||||
});
|
userId: existingCredential.userId,
|
||||||
|
username: existingCredential.username,
|
||||||
|
securityType: existingCredential.securityType,
|
||||||
|
savedAt: existingCredential.savedAt,
|
||||||
|
primaryImageTag: response.data.PrimaryImageTag ?? undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
} catch (e) {
|
.catch((e) => {
|
||||||
// Background fetch failed — app already rendered with cached data
|
// Expected, handled case (offline, or a token the server rejects —
|
||||||
console.warn("Background user fetch failed, using cached data:", e);
|
// the UI prompts re-login): warn, don't error. Log only
|
||||||
}
|
// status/message — never the raw error (axios errors carry the
|
||||||
} else {
|
// request config incl. the Authorization header / token).
|
||||||
setInitialLoaded(true);
|
console.warn(
|
||||||
|
"Background user validation failed:",
|
||||||
|
e?.response?.status ?? e?.message ?? "unknown error",
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
} finally {
|
||||||
setInitialLoaded(true);
|
setInitialLoaded(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -681,6 +800,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
removeServer: () => removeServerMutation.mutateAsync(),
|
removeServer: () => removeServerMutation.mutateAsync(),
|
||||||
login: (username, password, serverName, options) =>
|
login: (username, password, serverName, options) =>
|
||||||
loginMutation.mutateAsync({ username, password, serverName, options }),
|
loginMutation.mutateAsync({ username, password, serverName, options }),
|
||||||
|
saveCurrentAccount,
|
||||||
logout: () => logoutMutation.mutateAsync(),
|
logout: () => logoutMutation.mutateAsync(),
|
||||||
initiateQuickConnect,
|
initiateQuickConnect,
|
||||||
stopQuickConnectPolling,
|
stopQuickConnectPolling,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
@@ -18,6 +19,7 @@ interface ServerUrlContextValue {
|
|||||||
effectiveServerUrl: string | null;
|
effectiveServerUrl: string | null;
|
||||||
isUsingLocalUrl: boolean;
|
isUsingLocalUrl: boolean;
|
||||||
currentSSID: string | null;
|
currentSSID: string | null;
|
||||||
|
connectedToWifi: boolean;
|
||||||
refreshUrlState: () => void;
|
refreshUrlState: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +34,7 @@ interface Props {
|
|||||||
export function ServerUrlProvider({ children }: Props): React.ReactElement {
|
export function ServerUrlProvider({ children }: Props): React.ReactElement {
|
||||||
const api = useAtomValue(apiAtom);
|
const api = useAtomValue(apiAtom);
|
||||||
const { switchServerUrl } = useJellyfin();
|
const { switchServerUrl } = useJellyfin();
|
||||||
const { ssid, permissionStatus } = useWifiSSID();
|
const { ssid, connectedToWifi, permissionStatus } = useWifiSSID();
|
||||||
|
|
||||||
const [isUsingLocalUrl, setIsUsingLocalUrl] = useState(false);
|
const [isUsingLocalUrl, setIsUsingLocalUrl] = useState(false);
|
||||||
const [effectiveServerUrl, setEffectiveServerUrl] = useState<string | null>(
|
const [effectiveServerUrl, setEffectiveServerUrl] = useState<string | null>(
|
||||||
@@ -101,15 +103,25 @@ export function ServerUrlProvider({ children }: Props): React.ReactElement {
|
|||||||
};
|
};
|
||||||
}, [ssid, permissionStatus, evaluateAndSwitchUrl]);
|
}, [ssid, permissionStatus, evaluateAndSwitchUrl]);
|
||||||
|
|
||||||
|
const value = useMemo(
|
||||||
|
() => ({
|
||||||
|
effectiveServerUrl,
|
||||||
|
isUsingLocalUrl,
|
||||||
|
currentSSID: ssid,
|
||||||
|
connectedToWifi,
|
||||||
|
refreshUrlState,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
effectiveServerUrl,
|
||||||
|
isUsingLocalUrl,
|
||||||
|
ssid,
|
||||||
|
connectedToWifi,
|
||||||
|
refreshUrlState,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ServerUrlContext.Provider
|
<ServerUrlContext.Provider value={value}>
|
||||||
value={{
|
|
||||||
effectiveServerUrl,
|
|
||||||
isUsingLocalUrl,
|
|
||||||
currentSSID: ssid,
|
|
||||||
refreshUrlState,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</ServerUrlContext.Provider>
|
</ServerUrlContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
|
|||||||
const protocol = api.basePath.includes("https") ? "wss" : "ws";
|
const protocol = api.basePath.includes("https") ? "wss" : "ws";
|
||||||
const url = `${protocol}://${api.basePath
|
const url = `${protocol}://${api.basePath
|
||||||
.replace("https://", "")
|
.replace("https://", "")
|
||||||
.replace("http://", "")}/socket?api_key=${
|
.replace("http://", "")}/socket?ApiKey=${
|
||||||
api.accessToken
|
api.accessToken
|
||||||
}&deviceId=${deviceId}`;
|
}&deviceId=${deviceId}`;
|
||||||
|
|
||||||
|
|||||||
157
providers/WifiSsidProvider.tsx
Normal file
157
providers/WifiSsidProvider.tsx
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import type React from "react";
|
||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
type ReactNode,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
import { getSSID } from "@/modules/wifi-ssid";
|
||||||
|
|
||||||
|
export type PermissionStatus =
|
||||||
|
| "granted"
|
||||||
|
| "denied"
|
||||||
|
| "undetermined"
|
||||||
|
| "unavailable";
|
||||||
|
|
||||||
|
export interface UseWifiSSIDReturn {
|
||||||
|
ssid: string | null;
|
||||||
|
connectedToWifi: boolean;
|
||||||
|
permissionStatus: PermissionStatus;
|
||||||
|
requestPermission: () => Promise<boolean>;
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WifiSsidContext = createContext<UseWifiSSIDReturn | null>(null);
|
||||||
|
|
||||||
|
// WiFi SSID is not available on tvOS, and expo-location isn't needed there.
|
||||||
|
const Location = Platform.isTV ? null : require("expo-location");
|
||||||
|
|
||||||
|
const POLL_INTERVAL_MS = 10_000;
|
||||||
|
|
||||||
|
function mapLocationStatus(status: number | undefined): PermissionStatus {
|
||||||
|
if (!Location) return "unavailable";
|
||||||
|
switch (status) {
|
||||||
|
case Location.PermissionStatus?.GRANTED:
|
||||||
|
return "granted";
|
||||||
|
case Location.PermissionStatus?.DENIED:
|
||||||
|
return "denied";
|
||||||
|
default:
|
||||||
|
return "undetermined";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single source of truth for the current WiFi SSID and the location permission
|
||||||
|
* that gates it. State is shared via context so that granting permission in one
|
||||||
|
* place (e.g. the network settings screen) is reflected everywhere else — most
|
||||||
|
* importantly ServerUrlProvider, which switches between the local and remote
|
||||||
|
* server URLs based on the connected SSID.
|
||||||
|
*
|
||||||
|
* Previously each consumer created its own useWifiSSID instance with independent
|
||||||
|
* state, so the provider never learned that permission had been granted later
|
||||||
|
* in the session and reported "not connected to WiFi" forever.
|
||||||
|
*/
|
||||||
|
export function WifiSsidProvider({ children }: Props): React.ReactElement {
|
||||||
|
const [ssid, setSSID] = useState<string | null>(null);
|
||||||
|
const [connectedToWifi, setConnectedToWifi] = useState(false);
|
||||||
|
const [permissionStatus, setPermissionStatus] = useState<PermissionStatus>(
|
||||||
|
Platform.isTV ? "unavailable" : "undetermined",
|
||||||
|
);
|
||||||
|
const [isLoading, setIsLoading] = useState(!Platform.isTV);
|
||||||
|
|
||||||
|
const fetchSSID = useCallback(async () => {
|
||||||
|
if (Platform.isTV) return;
|
||||||
|
const result = await getSSID();
|
||||||
|
setSSID(result.ssid);
|
||||||
|
setConnectedToWifi(result.connectedToWifi);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const requestPermission = useCallback(async (): Promise<boolean> => {
|
||||||
|
if (Platform.isTV || !Location) {
|
||||||
|
setPermissionStatus("unavailable");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { status } = await Location.requestForegroundPermissionsAsync();
|
||||||
|
const newStatus = mapLocationStatus(status);
|
||||||
|
setPermissionStatus(newStatus);
|
||||||
|
return newStatus === "granted";
|
||||||
|
} catch {
|
||||||
|
setPermissionStatus("unavailable");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Check the location permission once on mount. The poll effect below takes
|
||||||
|
// over fetching the SSID whenever permission becomes "granted".
|
||||||
|
useEffect(() => {
|
||||||
|
if (Platform.isTV || !Location) {
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
async function initialize() {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const { status } = await Location.getForegroundPermissionsAsync();
|
||||||
|
if (cancelled) return;
|
||||||
|
setPermissionStatus(mapLocationStatus(status));
|
||||||
|
} catch {
|
||||||
|
if (!cancelled) setPermissionStatus("unavailable");
|
||||||
|
}
|
||||||
|
if (!cancelled) setIsLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize();
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Fetch the SSID immediately, then poll, whenever permission is granted.
|
||||||
|
// Because permissionStatus is shared, this also covers the case where
|
||||||
|
// permission was granted later (from another consumer) than this provider's
|
||||||
|
// mount time — the original root cause of the "not connected to WiFi" bug.
|
||||||
|
useEffect(() => {
|
||||||
|
if (Platform.isTV || permissionStatus !== "granted") return;
|
||||||
|
|
||||||
|
fetchSSID();
|
||||||
|
const interval = setInterval(fetchSSID, POLL_INTERVAL_MS);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [permissionStatus, fetchSSID]);
|
||||||
|
|
||||||
|
const value = useMemo(
|
||||||
|
() => ({
|
||||||
|
ssid,
|
||||||
|
connectedToWifi,
|
||||||
|
permissionStatus,
|
||||||
|
requestPermission,
|
||||||
|
isLoading,
|
||||||
|
}),
|
||||||
|
[ssid, connectedToWifi, permissionStatus, requestPermission, isLoading],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WifiSsidContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</WifiSsidContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useWifiSsid(): UseWifiSSIDReturn {
|
||||||
|
const context = useContext(WifiSsidContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useWifiSsid must be used within WifiSsidProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "المظهر",
|
"title": "المظهر",
|
||||||
"merge_next_up_continue_watching": "دمج تابع المشاهدة والتالي",
|
"merge_next_up_continue_watching": "دمج تابع المشاهدة والتالي",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "إخفاء زر جلسة البث عن بُعد",
|
"hide_remote_session_button": "إخفاء زر جلسة البث عن بُعد",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "المنطقة الآمنة لعناصر التحكم",
|
"safe_area_in_controls": "المنطقة الآمنة لعناصر التحكم",
|
||||||
"show_custom_menu_links": "إظهار روابط القائمة المخصصة",
|
"show_custom_menu_links": "إظهار روابط القائمة المخصصة",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "إظهار شريط العرض الكبير (تجريبي)",
|
"show_large_home_carousel": "إظهار شريط العرض الكبير (تجريبي)",
|
||||||
"hide_libraries": "إخفاء المكتبات",
|
"hide_libraries": "إخفاء المكتبات",
|
||||||
"select_libraries_you_want_to_hide": "اختر المكتبات التي تريد إخفاءها من تبويب المكتبة وأقسام الصفحة الرئيسية.",
|
"select_libraries_you_want_to_hide": "اختر المكتبات التي تريد إخفاءها من تبويب المكتبة وأقسام الصفحة الرئيسية.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Appearance",
|
"title": "Appearance",
|
||||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Hide Remote Session Button",
|
"hide_remote_session_button": "Hide Remote Session Button",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Àrea segura als controls",
|
"safe_area_in_controls": "Àrea segura als controls",
|
||||||
"show_custom_menu_links": "Mostrar enllaços del menú personalitzats",
|
"show_custom_menu_links": "Mostrar enllaços del menú personalitzats",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Oculta biblioteques",
|
"hide_libraries": "Oculta biblioteques",
|
||||||
"select_libraries_you_want_to_hide": "Seleccioneu les biblioteques que voleu ocultar de la pestanya Biblioteca i de les seccions de la pàgina d'inici.",
|
"select_libraries_you_want_to_hide": "Seleccioneu les biblioteques que voleu ocultar de la pestanya Biblioteca i de les seccions de la pàgina d'inici.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Appearance",
|
"title": "Appearance",
|
||||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Hide Remote Session Button",
|
"hide_remote_session_button": "Hide Remote Session Button",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Bezpečná oblast v ovládání",
|
"safe_area_in_controls": "Bezpečná oblast v ovládání",
|
||||||
"show_custom_menu_links": "Zobrazit vlastní Menu odkazy",
|
"show_custom_menu_links": "Zobrazit vlastní Menu odkazy",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Zobrazit velký přehled (beta)",
|
"show_large_home_carousel": "Zobrazit velký přehled (beta)",
|
||||||
"hide_libraries": "Skrýt knihovny",
|
"hide_libraries": "Skrýt knihovny",
|
||||||
"select_libraries_you_want_to_hide": "Vyberte knihovny, které chcete skrýt v záložce Knihovna a v sekcích domovské stránky.",
|
"select_libraries_you_want_to_hide": "Vyberte knihovny, které chcete skrýt v záložce Knihovna a v sekcích domovské stránky.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Appearance",
|
"title": "Appearance",
|
||||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Hide Remote Session Button",
|
"hide_remote_session_button": "Hide Remote Session Button",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Sikkert område i kontroller",
|
"safe_area_in_controls": "Sikkert område i kontroller",
|
||||||
"show_custom_menu_links": "Vis tilpassede menulinks",
|
"show_custom_menu_links": "Vis tilpassede menulinks",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Skjul biblioteker",
|
"hide_libraries": "Skjul biblioteker",
|
||||||
"select_libraries_you_want_to_hide": "Vælg de biblioteker, du ønsker at skjule fra fanen Bibliotek og startside sektionerne.",
|
"select_libraries_you_want_to_hide": "Vælg de biblioteker, du ønsker at skjule fra fanen Bibliotek og startside sektionerne.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Aussehen",
|
"title": "Aussehen",
|
||||||
"merge_next_up_continue_watching": "\"Weiterschauen\" und \"Als Nächstes\" kombinieren",
|
"merge_next_up_continue_watching": "\"Weiterschauen\" und \"Als Nächstes\" kombinieren",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Button für Remote-Sitzung ausblenden",
|
"hide_remote_session_button": "Button für Remote-Sitzung ausblenden",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Sicherer Bereich in den Steuerungen",
|
"safe_area_in_controls": "Sicherer Bereich in den Steuerungen",
|
||||||
"show_custom_menu_links": "Benutzerdefinierte Menülinks anzeigen",
|
"show_custom_menu_links": "Benutzerdefinierte Menülinks anzeigen",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Zeige große Startseiten-Übersicht (Beta)",
|
"show_large_home_carousel": "Zeige große Startseiten-Übersicht (Beta)",
|
||||||
"hide_libraries": "Bibliotheken ausblenden",
|
"hide_libraries": "Bibliotheken ausblenden",
|
||||||
"select_libraries_you_want_to_hide": "Bibliotheken auswählen die aus dem Bibliothekstab und der Startseite ausgeblendet werden sollen.",
|
"select_libraries_you_want_to_hide": "Bibliotheken auswählen die aus dem Bibliothekstab und der Startseite ausgeblendet werden sollen.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Appearance",
|
"title": "Appearance",
|
||||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Hide Remote Session Button",
|
"hide_remote_session_button": "Hide Remote Session Button",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Ασφαλής περιοχή σε χειριστήρια",
|
"safe_area_in_controls": "Ασφαλής περιοχή σε χειριστήρια",
|
||||||
"show_custom_menu_links": "Εμφάνιση Προσαρμοσμένων Συνδέσμων Μενού",
|
"show_custom_menu_links": "Εμφάνιση Προσαρμοσμένων Συνδέσμων Μενού",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Απόκρυψη Βιβλιοθηκών",
|
"hide_libraries": "Απόκρυψη Βιβλιοθηκών",
|
||||||
"select_libraries_you_want_to_hide": "Επιλέξτε τις βιβλιοθήκες που θέλετε να αποκρύψετε από την καρτέλα της Βιβλιοθήκης και τις ενότητες της αρχικής σελίδας.",
|
"select_libraries_you_want_to_hide": "Επιλέξτε τις βιβλιοθήκες που θέλετε να αποκρύψετε από την καρτέλα της Βιβλιοθήκης και τις ενότητες της αρχικής σελίδας.",
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
"login_button": "Log in",
|
"login_button": "Log in",
|
||||||
"quick_connect": "Quick Connect",
|
"quick_connect": "Quick Connect",
|
||||||
"enter_code_to_login": "Enter code {{code}} to log in",
|
"enter_code_to_login": "Enter code {{code}} to log in",
|
||||||
|
"quick_connect_instructions": "Enter this code on a signed-in device — you'll be logged in automatically.",
|
||||||
|
"tap_code_to_copy": "Tap the code to copy it",
|
||||||
|
"code_copied": "Code copied",
|
||||||
"failed_to_initiate_quick_connect": "Failed to initiate Quick Connect",
|
"failed_to_initiate_quick_connect": "Failed to initiate Quick Connect",
|
||||||
"got_it": "Got it",
|
"got_it": "Got it",
|
||||||
"connection_failed": "Connection failed",
|
"connection_failed": "Connection failed",
|
||||||
@@ -33,6 +36,7 @@
|
|||||||
"connect_button": "Connect",
|
"connect_button": "Connect",
|
||||||
"previous_servers": "Previous servers",
|
"previous_servers": "Previous servers",
|
||||||
"clear_button": "Clear all",
|
"clear_button": "Clear all",
|
||||||
|
"server_url": "Server URL",
|
||||||
"swipe_to_remove": "Swipe to remove",
|
"swipe_to_remove": "Swipe to remove",
|
||||||
"search_for_local_servers": "Search for local servers",
|
"search_for_local_servers": "Search for local servers",
|
||||||
"searching": "Searching...",
|
"searching": "Searching...",
|
||||||
@@ -56,6 +60,7 @@
|
|||||||
"save_account": {
|
"save_account": {
|
||||||
"title": "Save account",
|
"title": "Save account",
|
||||||
"save_for_later": "Save this account",
|
"save_for_later": "Save this account",
|
||||||
|
"not_saved": "Account was not saved",
|
||||||
"security_option": "Security option",
|
"security_option": "Security option",
|
||||||
"no_protection": "No protection",
|
"no_protection": "No protection",
|
||||||
"no_protection_desc": "Quick login without authentication",
|
"no_protection_desc": "Quick login without authentication",
|
||||||
@@ -118,6 +123,7 @@
|
|||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"settings_title": "Settings",
|
"settings_title": "Settings",
|
||||||
|
"disabled_by_admin": "Disabled by admin",
|
||||||
"log_out_button": "Log out",
|
"log_out_button": "Log out",
|
||||||
"switch_user": {
|
"switch_user": {
|
||||||
"title": "Switch user",
|
"title": "Switch user",
|
||||||
@@ -178,7 +184,10 @@
|
|||||||
"network_already_added": "Network already added",
|
"network_already_added": "Network already added",
|
||||||
"no_wifi_connected": "Not connected to Wi-Fi",
|
"no_wifi_connected": "Not connected to Wi-Fi",
|
||||||
"permission_denied": "Location permission denied",
|
"permission_denied": "Location permission denied",
|
||||||
"permission_denied_explanation": "Location permission is required to detect the Wi-Fi network for auto-switching. Please enable it in Settings."
|
"permission_denied_explanation": "Location permission is required to detect the Wi-Fi network for auto-switching. Please enable it in Settings.",
|
||||||
|
"ssid_hidden": "SSID hidden",
|
||||||
|
"location_off_description": "Connected to Wi-Fi, but the network name is hidden. Enable device Location services and grant the app location permission to detect it.",
|
||||||
|
"open_location_settings": "Open Location settings"
|
||||||
},
|
},
|
||||||
"user_info": {
|
"user_info": {
|
||||||
"user_info_title": "User info",
|
"user_info_title": "User info",
|
||||||
@@ -195,7 +204,8 @@
|
|||||||
"quick_connect_authorized": "Quick Connect authorized",
|
"quick_connect_authorized": "Quick Connect authorized",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"invalid_code": "Invalid code",
|
"invalid_code": "Invalid code",
|
||||||
"authorize": "Authorize"
|
"authorize": "Authorize",
|
||||||
|
"paste_code": "Paste code"
|
||||||
},
|
},
|
||||||
"media_controls": {
|
"media_controls": {
|
||||||
"media_controls_title": "Media controls",
|
"media_controls_title": "Media controls",
|
||||||
@@ -274,6 +284,10 @@
|
|||||||
"mpv_subtitle_margin_y": "Vertical margin",
|
"mpv_subtitle_margin_y": "Vertical margin",
|
||||||
"mpv_subtitle_align_x": "Horizontal align",
|
"mpv_subtitle_align_x": "Horizontal align",
|
||||||
"mpv_subtitle_align_y": "Vertical align",
|
"mpv_subtitle_align_y": "Vertical align",
|
||||||
|
"mpv_settings_title": "MPV Subtitle Settings",
|
||||||
|
"mpv_settings_description": "Advanced subtitle customization for MPV player",
|
||||||
|
"opaque_background": "Opaque Background",
|
||||||
|
"background_opacity": "Background Opacity",
|
||||||
"align": {
|
"align": {
|
||||||
"left": "Left",
|
"left": "Left",
|
||||||
"center": "Center",
|
"center": "Center",
|
||||||
@@ -375,6 +389,7 @@
|
|||||||
"toasts": {
|
"toasts": {
|
||||||
"saved": "Saved",
|
"saved": "Saved",
|
||||||
"refreshed": "Settings refreshed from server",
|
"refreshed": "Settings refreshed from server",
|
||||||
|
"refresh_failed": "Couldn't refresh settings from server",
|
||||||
"disabled": "Streamystats disabled"
|
"disabled": "Streamystats disabled"
|
||||||
},
|
},
|
||||||
"refresh_from_server": "Refresh settings from server"
|
"refresh_from_server": "Refresh settings from server"
|
||||||
@@ -415,7 +430,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",
|
||||||
@@ -442,10 +459,13 @@
|
|||||||
},
|
},
|
||||||
"sessions": {
|
"sessions": {
|
||||||
"title": "Sessions",
|
"title": "Sessions",
|
||||||
"no_active_sessions": "No active sessions"
|
"no_active_sessions": "No active sessions",
|
||||||
|
"select_session": "Select Session",
|
||||||
|
"now_playing": "Now playing:"
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"downloads_title": "Downloads",
|
"downloads_title": "Downloads",
|
||||||
|
"transcoding": "Transcoding",
|
||||||
"series": "Series",
|
"series": "Series",
|
||||||
"movies": "Movies",
|
"movies": "Movies",
|
||||||
"other_media": "Other media",
|
"other_media": "Other media",
|
||||||
@@ -501,6 +521,8 @@
|
|||||||
"none": "None",
|
"none": "None",
|
||||||
"track": "Track",
|
"track": "Track",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
"stop": "Stop",
|
||||||
|
"open_menu": "Open Menu",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
@@ -602,7 +624,31 @@
|
|||||||
},
|
},
|
||||||
"player": {
|
"player": {
|
||||||
"live": "LIVE",
|
"live": "LIVE",
|
||||||
|
"menu": {
|
||||||
|
"quality": "Quality",
|
||||||
|
"subtitles": "Subtitles",
|
||||||
|
"subtitle_scale": "Subtitle Scale",
|
||||||
|
"audio": "Audio",
|
||||||
|
"speed": "Speed",
|
||||||
|
"playback_options": "Playback Options",
|
||||||
|
"show_technical_info": "Show Technical Info",
|
||||||
|
"hide_technical_info": "Hide Technical Info"
|
||||||
|
},
|
||||||
|
"technical_info": {
|
||||||
|
"video": "Video:",
|
||||||
|
"audio": "Audio:",
|
||||||
|
"subtitle": "Subtitle:",
|
||||||
|
"bitrate": "Bitrate:",
|
||||||
|
"buffer_seconds": "Buffer: {{seconds}}s",
|
||||||
|
"vo": "VO:",
|
||||||
|
"dropped_frames": "Dropped: {{count}} frames",
|
||||||
|
"loading": "Loading..."
|
||||||
|
},
|
||||||
"mpv_player_title": "MPV player",
|
"mpv_player_title": "MPV player",
|
||||||
|
"aspect_ratio": "Aspect Ratio",
|
||||||
|
"aspect_ratio_original": "Original",
|
||||||
|
"hash_match": "Hash Match",
|
||||||
|
"still_watching": "Are you still watching?",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"failed_to_get_stream_url": "Failed to get the stream URL",
|
"failed_to_get_stream_url": "Failed to get the stream URL",
|
||||||
"an_error_occurred_while_playing_the_video": "An error occurred while playing the video. Check logs in settings.",
|
"an_error_occurred_while_playing_the_video": "An error occurred while playing the video. Check logs in settings.",
|
||||||
@@ -702,6 +748,7 @@
|
|||||||
"no_data_available": "No data available"
|
"no_data_available": "No data available"
|
||||||
},
|
},
|
||||||
"live_tv": {
|
"live_tv": {
|
||||||
|
"title": "Live TV",
|
||||||
"next": "Next",
|
"next": "Next",
|
||||||
"previous": "Previous",
|
"previous": "Previous",
|
||||||
"coming_soon": "Coming soon",
|
"coming_soon": "Coming soon",
|
||||||
@@ -773,7 +820,7 @@
|
|||||||
"request_selected": "Request selected",
|
"request_selected": "Request selected",
|
||||||
"n_selected": "{{count}} selected",
|
"n_selected": "{{count}} selected",
|
||||||
"toasts": {
|
"toasts": {
|
||||||
"jellyseer_does_not_meet_requirements": "Seerr server does not meet minimum version requirements! Please update to at least 2.0.0",
|
"jellyseerr_does_not_meet_requirements": "Seerr server does not meet minimum version requirements! Please update to at least 2.0.0",
|
||||||
"jellyseerr_test_failed": "Seerr test failed. Please try again.",
|
"jellyseerr_test_failed": "Seerr test failed. Please try again.",
|
||||||
"failed_to_test_jellyseerr_server_url": "Failed to test Seerr server url",
|
"failed_to_test_jellyseerr_server_url": "Failed to test Seerr server url",
|
||||||
"issue_submitted": "Issue submitted!",
|
"issue_submitted": "Issue submitted!",
|
||||||
@@ -786,6 +833,16 @@
|
|||||||
"failed_to_decline_request": "Failed to decline request"
|
"failed_to_decline_request": "Failed to decline request"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"accessibility": {
|
||||||
|
"play_button": "Play button",
|
||||||
|
"play_hint": "Tap to play the media",
|
||||||
|
"toggle_orientation": "Toggle screen orientation",
|
||||||
|
"toggle_orientation_hint": "Toggles the screen orientation between portrait and landscape"
|
||||||
|
},
|
||||||
|
"not_found": {
|
||||||
|
"title": "This screen doesn't exist.",
|
||||||
|
"go_home": "Go to home screen!"
|
||||||
|
},
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
@@ -796,6 +853,12 @@
|
|||||||
},
|
},
|
||||||
"music": {
|
"music": {
|
||||||
"title": "Music",
|
"title": "Music",
|
||||||
|
"no_track_playing": "No track playing",
|
||||||
|
"queue_empty": "Queue is empty",
|
||||||
|
"playing_from_queue": "Playing from queue",
|
||||||
|
"up_next": "Up next",
|
||||||
|
"now_playing": "Now Playing",
|
||||||
|
"missing_library_id": "Missing music library id.",
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"suggestions": "Suggestions",
|
"suggestions": "Suggestions",
|
||||||
"albums": "Albums",
|
"albums": "Albums",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Apariencia",
|
"title": "Apariencia",
|
||||||
"merge_next_up_continue_watching": "Fusionar continuar viendo y siguiente",
|
"merge_next_up_continue_watching": "Fusionar continuar viendo y siguiente",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Ocultar botón de sesión remota",
|
"hide_remote_session_button": "Ocultar botón de sesión remota",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Área segura en controles",
|
"safe_area_in_controls": "Área segura en controles",
|
||||||
"show_custom_menu_links": "Mostrar enlaces de menú personalizados",
|
"show_custom_menu_links": "Mostrar enlaces de menú personalizados",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Mostrar carrusel del menú principal grande (beta)",
|
"show_large_home_carousel": "Mostrar carrusel del menú principal grande (beta)",
|
||||||
"hide_libraries": "Ocultar bibliotecas",
|
"hide_libraries": "Ocultar bibliotecas",
|
||||||
"select_libraries_you_want_to_hide": "Selecciona las bibliotecas que quieres ocultar de la pestaña Bibliotecas y de Inicio.",
|
"select_libraries_you_want_to_hide": "Selecciona las bibliotecas que quieres ocultar de la pestaña Bibliotecas y de Inicio.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Ulkoasu",
|
"title": "Ulkoasu",
|
||||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Hide Remote Session Button",
|
"hide_remote_session_button": "Hide Remote Session Button",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Turvallinen alue ohjaimissa",
|
"safe_area_in_controls": "Turvallinen alue ohjaimissa",
|
||||||
"show_custom_menu_links": "Näytä mukautetut valikkolinkit",
|
"show_custom_menu_links": "Näytä mukautetut valikkolinkit",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Näytä suuri kotikaruselli (beta)",
|
"show_large_home_carousel": "Näytä suuri kotikaruselli (beta)",
|
||||||
"hide_libraries": "Piilota kirjastot",
|
"hide_libraries": "Piilota kirjastot",
|
||||||
"select_libraries_you_want_to_hide": "Valitse kirjastot, jotka haluat piilottaa Kirjasto-välilehdeltä ja etusivun osioista.",
|
"select_libraries_you_want_to_hide": "Valitse kirjastot, jotka haluat piilottaa Kirjasto-välilehdeltä ja etusivun osioista.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Apparence",
|
"title": "Apparence",
|
||||||
"merge_next_up_continue_watching": "Fusionner « Continuer à regarder » et « À suivre »",
|
"merge_next_up_continue_watching": "Fusionner « Continuer à regarder » et « À suivre »",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Masquer le bouton de session distante",
|
"hide_remote_session_button": "Masquer le bouton de session distante",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Arrière-plan d'accueil dynamique",
|
"show_home_backdrop": "Arrière-plan d'accueil dynamique",
|
||||||
"show_hero_carousel": "Carrousel principal",
|
"show_hero_carousel": "Carrousel principal",
|
||||||
"show_series_poster_on_episode": "Afficher l'affiche de la série sur les épisodes",
|
"show_series_poster_on_episode": "Afficher l'affiche de la série sur les épisodes",
|
||||||
@@ -152,7 +156,7 @@
|
|||||||
"title": "Réseau",
|
"title": "Réseau",
|
||||||
"local_network": "Réseau local",
|
"local_network": "Réseau local",
|
||||||
"auto_switch_enabled": "Basculement automatique à la maison",
|
"auto_switch_enabled": "Basculement automatique à la maison",
|
||||||
"auto_switch_description": "Basculer automatiquement vers l'URL locale lorsque vous êtes connecté au Wi-Fi de la maison",
|
"auto_switch_description": "Basculer automatiquement vers l'URL locale lorsque vous êtes connecté au Wi-Fi domestique",
|
||||||
"local_url": "URL locale",
|
"local_url": "URL locale",
|
||||||
"local_url_hint": "Entrez l'adresse de votre serveur local (par exemple http://192.168.1.100:8096)",
|
"local_url_hint": "Entrez l'adresse de votre serveur local (par exemple http://192.168.1.100:8096)",
|
||||||
"local_url_placeholder": "http://192.168.1.100:8096",
|
"local_url_placeholder": "http://192.168.1.100:8096",
|
||||||
@@ -173,7 +177,7 @@
|
|||||||
"network_added": "Réseau ajouté",
|
"network_added": "Réseau ajouté",
|
||||||
"network_already_added": "Réseau déjà ajouté",
|
"network_already_added": "Réseau déjà ajouté",
|
||||||
"no_wifi_connected": "Non connecté au Wi-Fi",
|
"no_wifi_connected": "Non connecté au Wi-Fi",
|
||||||
"permission_denied": "Autorisation de localisation refusée",
|
"permission_denied": "Autorisation d'accès à la localisation refusée",
|
||||||
"permission_denied_explanation": "Une autorisation de localisation est requise pour détecter le réseau Wi-Fi afin de basculer automatiquement. Veuillez l'activer dans les paramètres."
|
"permission_denied_explanation": "Une autorisation de localisation est requise pour détecter le réseau Wi-Fi afin de basculer automatiquement. Veuillez l'activer dans les paramètres."
|
||||||
},
|
},
|
||||||
"user_info": {
|
"user_info": {
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Zone de sécurité dans les contrôles",
|
"safe_area_in_controls": "Zone de sécurité dans les contrôles",
|
||||||
"show_custom_menu_links": "Afficher les liens personnalisés",
|
"show_custom_menu_links": "Afficher les liens personnalisés",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Afficher le grand carrousel d’accueil (bêta)",
|
"show_large_home_carousel": "Afficher le grand carrousel d’accueil (bêta)",
|
||||||
"hide_libraries": "Masquer les bibliothèques",
|
"hide_libraries": "Masquer les bibliothèques",
|
||||||
"select_libraries_you_want_to_hide": "Sélectionnez les bibliothèques que vous souhaitez masquer dans l'onglet Bibliothèque et les sections de la page d'accueil.",
|
"select_libraries_you_want_to_hide": "Sélectionnez les bibliothèques que vous souhaitez masquer dans l'onglet Bibliothèque et les sections de la page d'accueil.",
|
||||||
@@ -362,7 +367,7 @@
|
|||||||
"features_title": "Fonctionnalités",
|
"features_title": "Fonctionnalités",
|
||||||
"enable_movie_recommendations": "Recommandations de films",
|
"enable_movie_recommendations": "Recommandations de films",
|
||||||
"enable_series_recommendations": "Recommandations de séries",
|
"enable_series_recommendations": "Recommandations de séries",
|
||||||
"enable_promoted_watchlists": "Listes de suivi promues",
|
"enable_promoted_watchlists": "Listes de lecture promues",
|
||||||
"hide_watchlists_tab": "Masquer l'onglet des listes de suivi",
|
"hide_watchlists_tab": "Masquer l'onglet des listes de suivi",
|
||||||
"home_sections_hint": "Afficher des recommandations personnalisées et des listes de suivi promues de Streamystats sur la page d’accueil.",
|
"home_sections_hint": "Afficher des recommandations personnalisées et des listes de suivi promues de Streamystats sur la page d’accueil.",
|
||||||
"recommended_movies": "Films recommandés",
|
"recommended_movies": "Films recommandés",
|
||||||
@@ -630,7 +635,7 @@
|
|||||||
"skip_intro": "Passer l'intro",
|
"skip_intro": "Passer l'intro",
|
||||||
"skip_credits": "Passer le générique",
|
"skip_credits": "Passer le générique",
|
||||||
"stopPlayback": "Arrêter la lecture",
|
"stopPlayback": "Arrêter la lecture",
|
||||||
"stopPlayingTitle": "Arrêter de lire \"{{title}}\" ?",
|
"stopPlayingTitle": "Arrêter de lire « {{title}} » ?",
|
||||||
"stopPlayingConfirm": "Êtes-vous sûr de vouloir arrêter la lecture ?",
|
"stopPlayingConfirm": "Êtes-vous sûr de vouloir arrêter la lecture ?",
|
||||||
"downloaded": "Téléchargé",
|
"downloaded": "Téléchargé",
|
||||||
"missing_parameters": "Paramètres de lecture manquants"
|
"missing_parameters": "Paramètres de lecture manquants"
|
||||||
@@ -704,7 +709,7 @@
|
|||||||
"shows": "Émissions",
|
"shows": "Émissions",
|
||||||
"movies": "Films",
|
"movies": "Films",
|
||||||
"sports": "Sports",
|
"sports": "Sports",
|
||||||
"for_kids": "Pour enfants",
|
"for_kids": "Pour les enfants",
|
||||||
"news": "Actualités",
|
"news": "Actualités",
|
||||||
"page_of": "Page {{current}} sur {{total}}",
|
"page_of": "Page {{current}} sur {{total}}",
|
||||||
"no_programs": "Aucun programme disponible",
|
"no_programs": "Aucun programme disponible",
|
||||||
@@ -730,7 +735,7 @@
|
|||||||
"submit_button": "Soumettre",
|
"submit_button": "Soumettre",
|
||||||
"report_issue_button": "Signaler un problème",
|
"report_issue_button": "Signaler un problème",
|
||||||
"request_button": "Demander",
|
"request_button": "Demander",
|
||||||
"are_you_sure_you_want_to_request_all_seasons": "Êtes-vous sûr de vouloir demander toutes les saisons ?",
|
"are_you_sure_you_want_to_request_all_seasons": "Voulez-vous vraiment demander toutes les saisons ?",
|
||||||
"failed_to_login": "Échec de la connexion",
|
"failed_to_login": "Échec de la connexion",
|
||||||
"cast": "Distribution",
|
"cast": "Distribution",
|
||||||
"details": "Détails",
|
"details": "Détails",
|
||||||
@@ -796,7 +801,7 @@
|
|||||||
"albums": "Albums",
|
"albums": "Albums",
|
||||||
"artists": "Artistes",
|
"artists": "Artistes",
|
||||||
"playlists": "Playlists",
|
"playlists": "Playlists",
|
||||||
"tracks": "morceaux"
|
"tracks": "Morceaux"
|
||||||
},
|
},
|
||||||
"recently_added": "Ajoutés récemment",
|
"recently_added": "Ajoutés récemment",
|
||||||
"recently_played": "Récemment joué",
|
"recently_played": "Récemment joué",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "מראה",
|
"title": "מראה",
|
||||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Hide Remote Session Button",
|
"hide_remote_session_button": "Hide Remote Session Button",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "איזור בטוח בפקדים",
|
"safe_area_in_controls": "איזור בטוח בפקדים",
|
||||||
"show_custom_menu_links": "הצג קישורים לתפריטים מותאמים אישית",
|
"show_custom_menu_links": "הצג קישורים לתפריטים מותאמים אישית",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "הצג קרוסלה גדולה במסך הבית (בטא)",
|
"show_large_home_carousel": "הצג קרוסלה גדולה במסך הבית (בטא)",
|
||||||
"hide_libraries": "הסתר ספריות",
|
"hide_libraries": "הסתר ספריות",
|
||||||
"select_libraries_you_want_to_hide": "בחר את הספריות שתרצה להסתיר ממסך הספריות וגם ממסך הבית.",
|
"select_libraries_you_want_to_hide": "בחר את הספריות שתרצה להסתיר ממסך הספריות וגם ממסך הבית.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Appearance",
|
"title": "Appearance",
|
||||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Hide Remote Session Button",
|
"hide_remote_session_button": "Hide Remote Session Button",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Biztonsági Sáv a Vezérlőkben",
|
"safe_area_in_controls": "Biztonsági Sáv a Vezérlőkben",
|
||||||
"show_custom_menu_links": "Egyéni Menülinkek Megjelenítése",
|
"show_custom_menu_links": "Egyéni Menülinkek Megjelenítése",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Könyvtárak Elrejtése",
|
"hide_libraries": "Könyvtárak Elrejtése",
|
||||||
"select_libraries_you_want_to_hide": "Válaszd ki azokat a könyvtárakat, amelyeket el szeretnél rejteni a Könyvtár fülön és a kezdőlapon.",
|
"select_libraries_you_want_to_hide": "Válaszd ki azokat a könyvtárakat, amelyeket el szeretnél rejteni a Könyvtár fülön és a kezdőlapon.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Aspetto",
|
"title": "Aspetto",
|
||||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Hide Remote Session Button",
|
"hide_remote_session_button": "Hide Remote Session Button",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Area sicura per i controlli",
|
"safe_area_in_controls": "Area sicura per i controlli",
|
||||||
"show_custom_menu_links": "Mostra i link del menu personalizzato",
|
"show_custom_menu_links": "Mostra i link del menu personalizzato",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Mostra Carosello Grande nella Home (beta)",
|
"show_large_home_carousel": "Mostra Carosello Grande nella Home (beta)",
|
||||||
"hide_libraries": "Nascondi Librerie",
|
"hide_libraries": "Nascondi Librerie",
|
||||||
"select_libraries_you_want_to_hide": "Selezionate le librerie che volete nascondere dalla scheda Libreria e dalle sezioni della pagina iniziale.",
|
"select_libraries_you_want_to_hide": "Selezionate le librerie che volete nascondere dalla scheda Libreria e dalle sezioni della pagina iniziale.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Appearance",
|
"title": "Appearance",
|
||||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Hide Remote Session Button",
|
"hide_remote_session_button": "Hide Remote Session Button",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "コントロールの安全エリア",
|
"safe_area_in_controls": "コントロールの安全エリア",
|
||||||
"show_custom_menu_links": "カスタムメニューのリンクを表示",
|
"show_custom_menu_links": "カスタムメニューのリンクを表示",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "大きなヒーロー(Beta)",
|
"show_large_home_carousel": "大きなヒーロー(Beta)",
|
||||||
"hide_libraries": "ライブラリを非表示",
|
"hide_libraries": "ライブラリを非表示",
|
||||||
"select_libraries_you_want_to_hide": "ライブラリタブとホームページセクションから非表示にするライブラリを選択します。",
|
"select_libraries_you_want_to_hide": "ライブラリタブとホームページセクションから非表示にするライブラリを選択します。",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "화면 스타일",
|
"title": "화면 스타일",
|
||||||
"merge_next_up_continue_watching": "[이어보기]와 [다음 보기] 합치기",
|
"merge_next_up_continue_watching": "[이어보기]와 [다음 보기] 합치기",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "원격 세션 버튼 숨기기",
|
"hide_remote_session_button": "원격 세션 버튼 숨기기",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "컨트롤 안전 영역",
|
"safe_area_in_controls": "컨트롤 안전 영역",
|
||||||
"show_custom_menu_links": "사용자 지정 메뉴 링크 표시",
|
"show_custom_menu_links": "사용자 지정 메뉴 링크 표시",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "대형 홈 슬라이드 배너 표시 (베타)",
|
"show_large_home_carousel": "대형 홈 슬라이드 배너 표시 (베타)",
|
||||||
"hide_libraries": "라이브러리 숨기기",
|
"hide_libraries": "라이브러리 숨기기",
|
||||||
"select_libraries_you_want_to_hide": "Select the libraries you want to hide from the Library tab and home page sections.",
|
"select_libraries_you_want_to_hide": "Select the libraries you want to hide from the Library tab and home page sections.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Weergave",
|
"title": "Weergave",
|
||||||
"merge_next_up_continue_watching": "Doorgaan met kijken & Volgende samenvoegen",
|
"merge_next_up_continue_watching": "Doorgaan met kijken & Volgende samenvoegen",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Verberg Knop voor Externe Sessie",
|
"hide_remote_session_button": "Verberg Knop voor Externe Sessie",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Veilig gebied in bedieningen",
|
"safe_area_in_controls": "Veilig gebied in bedieningen",
|
||||||
"show_custom_menu_links": "Aangepaste menulinks tonen",
|
"show_custom_menu_links": "Aangepaste menulinks tonen",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Toon grote carrousel op startpagina (bèta)",
|
"show_large_home_carousel": "Toon grote carrousel op startpagina (bèta)",
|
||||||
"hide_libraries": "Verberg Bibliotheken",
|
"hide_libraries": "Verberg Bibliotheken",
|
||||||
"select_libraries_you_want_to_hide": "Selecteer de bibliotheken die je wil verbergen van de Bibliotheektab en hoofdpagina onderdelen.",
|
"select_libraries_you_want_to_hide": "Selecteer de bibliotheken die je wil verbergen van de Bibliotheektab en hoofdpagina onderdelen.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Appearance",
|
"title": "Appearance",
|
||||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Hide Remote Session Button",
|
"hide_remote_session_button": "Hide Remote Session Button",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Sikker sone i kontroller",
|
"safe_area_in_controls": "Sikker sone i kontroller",
|
||||||
"show_custom_menu_links": "Vis tilpassede menylenker",
|
"show_custom_menu_links": "Vis tilpassede menylenker",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Skjul biblioteker",
|
"hide_libraries": "Skjul biblioteker",
|
||||||
"select_libraries_you_want_to_hide": "Velg bibliotekene du vil skjule deg for Biblioteket og avsnittene for hjemmesider.",
|
"select_libraries_you_want_to_hide": "Velg bibliotekene du vil skjule deg for Biblioteket og avsnittene for hjemmesider.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Wygląd",
|
"title": "Wygląd",
|
||||||
"merge_next_up_continue_watching": "Połącz Oglądaj dalej i Następne",
|
"merge_next_up_continue_watching": "Połącz Oglądaj dalej i Następne",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Ukryj przycisk Zdalnej Sesji",
|
"hide_remote_session_button": "Ukryj przycisk Zdalnej Sesji",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Bezpieczny obszar w kontrolkach",
|
"safe_area_in_controls": "Bezpieczny obszar w kontrolkach",
|
||||||
"show_custom_menu_links": "Pokaż niestandardowe odnośniki w menu",
|
"show_custom_menu_links": "Pokaż niestandardowe odnośniki w menu",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Wyświetl Dużą Karuzelę na ekranie głównym (beta)",
|
"show_large_home_carousel": "Wyświetl Dużą Karuzelę na ekranie głównym (beta)",
|
||||||
"hide_libraries": "Ukryj biblioteki",
|
"hide_libraries": "Ukryj biblioteki",
|
||||||
"select_libraries_you_want_to_hide": "Wybierz biblioteki, które chcesz ukryć na karcie Biblioteka i w sekcjach strony głównej.",
|
"select_libraries_you_want_to_hide": "Wybierz biblioteki, które chcesz ukryć na karcie Biblioteka i w sekcjach strony głównej.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Aparência",
|
"title": "Aparência",
|
||||||
"merge_next_up_continue_watching": "Mesclar Continuar Assistindo e Próximo",
|
"merge_next_up_continue_watching": "Mesclar Continuar Assistindo e Próximo",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Esconder botão de sessão remota",
|
"hide_remote_session_button": "Esconder botão de sessão remota",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Área segura nos controles",
|
"safe_area_in_controls": "Área segura nos controles",
|
||||||
"show_custom_menu_links": "Mostrar Links de Menu Personalizado",
|
"show_custom_menu_links": "Mostrar Links de Menu Personalizado",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Mostrar Carrossel Grande (beta)",
|
"show_large_home_carousel": "Mostrar Carrossel Grande (beta)",
|
||||||
"hide_libraries": "Ocultar bibliotecas",
|
"hide_libraries": "Ocultar bibliotecas",
|
||||||
"select_libraries_you_want_to_hide": "Select the libraries you want to hide from the Library tab and home page sections.",
|
"select_libraries_you_want_to_hide": "Select the libraries you want to hide from the Library tab and home page sections.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Aspect",
|
"title": "Aspect",
|
||||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Hide Remote Session Button",
|
"hide_remote_session_button": "Hide Remote Session Button",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Zona sigură pentru controale",
|
"safe_area_in_controls": "Zona sigură pentru controale",
|
||||||
"show_custom_menu_links": "Afișează link-uri personalizate în meniu",
|
"show_custom_menu_links": "Afișează link-uri personalizate în meniu",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Arată Caruselul Media Mare (beta)",
|
"show_large_home_carousel": "Arată Caruselul Media Mare (beta)",
|
||||||
"hide_libraries": "Ascunde bibliotecile",
|
"hide_libraries": "Ascunde bibliotecile",
|
||||||
"select_libraries_you_want_to_hide": "Selectează bibliotecile pe care dorești să le ascunzi din fila Bibliotecă și din secțiunile paginii principale.",
|
"select_libraries_you_want_to_hide": "Selectează bibliotecile pe care dorești să le ascunzi din fila Bibliotecă și din secțiunile paginii principale.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Внешний вид",
|
"title": "Внешний вид",
|
||||||
"merge_next_up_continue_watching": "Объединить «Продолжить» и «Далее»",
|
"merge_next_up_continue_watching": "Объединить «Продолжить» и «Далее»",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Скрыть кнопку «Удалённый сеанс»",
|
"hide_remote_session_button": "Скрыть кнопку «Удалённый сеанс»",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Безопасная зона в элементах управления",
|
"safe_area_in_controls": "Безопасная зона в элементах управления",
|
||||||
"show_custom_menu_links": "Показать ссылки пользовательского меню",
|
"show_custom_menu_links": "Показать ссылки пользовательского меню",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Показывать большую карусель (beta)",
|
"show_large_home_carousel": "Показывать большую карусель (beta)",
|
||||||
"hide_libraries": "Скрыть библиотеки",
|
"hide_libraries": "Скрыть библиотеки",
|
||||||
"select_libraries_you_want_to_hide": "Выберите Библиотеки, которое хотите спрятать из вкладки Библиотеки и домашней страницы.",
|
"select_libraries_you_want_to_hide": "Выберите Библиотеки, которое хотите спрятать из вкладки Библиотеки и домашней страницы.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Utseende",
|
"title": "Utseende",
|
||||||
"merge_next_up_continue_watching": "Slå ihop Fortsätt titta och nästa avsnitt",
|
"merge_next_up_continue_watching": "Slå ihop Fortsätt titta och nästa avsnitt",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Dölj fjärrsessionsknapp",
|
"hide_remote_session_button": "Dölj fjärrsessionsknapp",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamisk hembakgrund",
|
"show_home_backdrop": "Dynamisk hembakgrund",
|
||||||
"show_hero_carousel": "Hjältekarusell",
|
"show_hero_carousel": "Hjältekarusell",
|
||||||
"show_series_poster_on_episode": "Visa serieaffisch på avsnitt",
|
"show_series_poster_on_episode": "Visa serieaffisch på avsnitt",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Säkert område i kontrollerna",
|
"safe_area_in_controls": "Säkert område i kontrollerna",
|
||||||
"show_custom_menu_links": "Visa anpassade menylänkar",
|
"show_custom_menu_links": "Visa anpassade menylänkar",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Visa toppbanner (beta)",
|
"show_large_home_carousel": "Visa toppbanner (beta)",
|
||||||
"hide_libraries": "Dölj bibliotek",
|
"hide_libraries": "Dölj bibliotek",
|
||||||
"select_libraries_you_want_to_hide": "Välj de bibliotek du vill dölja på fliken Bibliotek och på startsidan.",
|
"select_libraries_you_want_to_hide": "Välj de bibliotek du vill dölja på fliken Bibliotek och på startsidan.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "ปรับแต่งลักษณะภายนอก",
|
"title": "ปรับแต่งลักษณะภายนอก",
|
||||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Hide Remote Session Button",
|
"hide_remote_session_button": "Hide Remote Session Button",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Safe Area in Controls",
|
"safe_area_in_controls": "Safe Area in Controls",
|
||||||
"show_custom_menu_links": "Show Custom Menu Links",
|
"show_custom_menu_links": "Show Custom Menu Links",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Hide Libraries",
|
"hide_libraries": "Hide Libraries",
|
||||||
"select_libraries_you_want_to_hide": "Select the libraries you want to hide from the Library tab and home page sections.",
|
"select_libraries_you_want_to_hide": "Select the libraries you want to hide from the Library tab and home page sections.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Appearance",
|
"title": "Appearance",
|
||||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Hide Remote Session Button",
|
"hide_remote_session_button": "Hide Remote Session Button",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "SeHlawDaq yot QIH",
|
"safe_area_in_controls": "SeHlawDaq yot QIH",
|
||||||
"show_custom_menu_links": "menuDaq ret teqlu' yInej",
|
"show_custom_menu_links": "menuDaq ret teqlu' yInej",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "De'wI' bom yIQIj",
|
"hide_libraries": "De'wI' bom yIQIj",
|
||||||
"select_libraries_you_want_to_hide": "De'wI' bom Danej QIj yIwIv.",
|
"select_libraries_you_want_to_hide": "De'wI' bom Danej QIj yIwIv.",
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
"error_title": "Hata",
|
"error_title": "Hata",
|
||||||
"login_title": "Giriş yap",
|
"login_title": "Giriş yap",
|
||||||
"login_to_title": " 'e giriş yap",
|
"login_to_title": " 'e giriş yap",
|
||||||
"select_user": "Select a user to log in",
|
"select_user": "Giriş yapmak için bir kullanıcı seçin",
|
||||||
"add_user_to_login": "Add a user to log in",
|
"add_user_to_login": "Giriş için kullanıcı ekleyin",
|
||||||
"add_user": "Add User",
|
"add_user": "Add User",
|
||||||
"username_placeholder": "Kullanıcı adı",
|
"username_placeholder": "Kullanıcı adı",
|
||||||
"password_placeholder": "Şifre",
|
"password_placeholder": "Şifre",
|
||||||
@@ -47,9 +47,9 @@
|
|||||||
"add_account": "Hesap Ekle",
|
"add_account": "Hesap Ekle",
|
||||||
"remove_account_description": "{{username}} için kayıtlı bilgiler kaldırılacaktır.",
|
"remove_account_description": "{{username}} için kayıtlı bilgiler kaldırılacaktır.",
|
||||||
"remove_server": "Remove Server",
|
"remove_server": "Remove Server",
|
||||||
"remove_server_description": "This will remove {{server}} and all saved accounts from your list.",
|
"remove_server_description": "{{server}} ve tüm kaydedilen hesapları listenizden kaldırır.",
|
||||||
"select_your_server": "Select Your Server",
|
"select_your_server": "Select Your Server",
|
||||||
"add_server_to_get_started": "Add a server to get started",
|
"add_server_to_get_started": "Başlamak için bir sunucu ekleyin",
|
||||||
"add_server": "Add Server",
|
"add_server": "Add Server",
|
||||||
"change_server": "Change Server"
|
"change_server": "Change Server"
|
||||||
},
|
},
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
"oops": "Hups!",
|
"oops": "Hups!",
|
||||||
"error_message": "Bir şeyler ters gitti.\nLütfen çıkış yapıp tekrar giriş yapın.",
|
"error_message": "Bir şeyler ters gitti.\nLütfen çıkış yapıp tekrar giriş yapın.",
|
||||||
"continue_watching": "İzlemeye Devam Et",
|
"continue_watching": "İzlemeye Devam Et",
|
||||||
"continue": "Continue",
|
"continue": "Devam et",
|
||||||
"next_up": "Sonraki",
|
"next_up": "Sonraki",
|
||||||
"continue_and_next_up": "İzlemeye Devam Et & Sıradakiler",
|
"continue_and_next_up": "İzlemeye Devam Et & Sıradakiler",
|
||||||
"recently_added_in": "{{libraryName}} Kütüphanesine Son Eklenenler",
|
"recently_added_in": "{{libraryName}} Kütüphanesine Son Eklenenler",
|
||||||
@@ -121,9 +121,9 @@
|
|||||||
"log_out_button": "Çıkış Yap",
|
"log_out_button": "Çıkış Yap",
|
||||||
"switch_user": {
|
"switch_user": {
|
||||||
"title": "Switch User",
|
"title": "Switch User",
|
||||||
"account": "Account",
|
"account": "Hesap",
|
||||||
"switch_user": "Switch User on This Server",
|
"switch_user": "Switch User on This Server",
|
||||||
"current": "current"
|
"current": "güncel"
|
||||||
},
|
},
|
||||||
"categories": {
|
"categories": {
|
||||||
"title": "Kategoriler"
|
"title": "Kategoriler"
|
||||||
@@ -137,15 +137,19 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Görünüm",
|
"title": "Görünüm",
|
||||||
"merge_next_up_continue_watching": "İzlemeye Devam Et & Sıradakiler'i birleştir",
|
"merge_next_up_continue_watching": "İzlemeye Devam Et & Sıradakiler'i birleştir",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Uzak Oturum Butonunu Gizle",
|
"hide_remote_session_button": "Uzak Oturum Butonunu Gizle",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
"theme_music": "Theme Music",
|
"theme_music": "Theme Music",
|
||||||
"display_size": "Display Size",
|
"display_size": "Display Size",
|
||||||
"display_size_small": "Small",
|
"display_size_small": "Küçük",
|
||||||
"display_size_default": "Default",
|
"display_size_default": "Varsayılan",
|
||||||
"display_size_large": "Large",
|
"display_size_large": "Büyük",
|
||||||
"display_size_extra_large": "Extra Large"
|
"display_size_extra_large": "Extra Large"
|
||||||
},
|
},
|
||||||
"network": {
|
"network": {
|
||||||
@@ -203,8 +207,8 @@
|
|||||||
"title": "Buffer Settings",
|
"title": "Buffer Settings",
|
||||||
"cache_mode": "Cache Mode",
|
"cache_mode": "Cache Mode",
|
||||||
"cache_auto": "Auto",
|
"cache_auto": "Auto",
|
||||||
"cache_yes": "Enabled",
|
"cache_yes": "Açık",
|
||||||
"cache_no": "Disabled",
|
"cache_no": "Kapalı",
|
||||||
"buffer_duration": "Buffer Duration",
|
"buffer_duration": "Buffer Duration",
|
||||||
"max_cache_size": "Max Cache Size",
|
"max_cache_size": "Max Cache Size",
|
||||||
"max_backward_cache": "Max Backward Cache"
|
"max_backward_cache": "Max Backward Cache"
|
||||||
@@ -212,7 +216,7 @@
|
|||||||
"vo_driver": {
|
"vo_driver": {
|
||||||
"title": "Video Output",
|
"title": "Video Output",
|
||||||
"vo_mode": "VO Driver",
|
"vo_mode": "VO Driver",
|
||||||
"gpu_next": "gpu-next (Recommended)",
|
"gpu_next": "gpu-next (Önerilen)",
|
||||||
"gpu": "gpu"
|
"gpu": "gpu"
|
||||||
},
|
},
|
||||||
"gesture_controls": {
|
"gesture_controls": {
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Kontrollerde Güvenli Alan",
|
"safe_area_in_controls": "Kontrollerde Güvenli Alan",
|
||||||
"show_custom_menu_links": "Özel Menü Bağlantılarını Göster",
|
"show_custom_menu_links": "Özel Menü Bağlantılarını Göster",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Kütüphaneleri Gizle",
|
"hide_libraries": "Kütüphaneleri Gizle",
|
||||||
"select_libraries_you_want_to_hide": "Kütüphane sekmesinden ve ana sayfa bölümlerinden gizlemek istediğiniz kütüphaneleri seçin.",
|
"select_libraries_you_want_to_hide": "Kütüphane sekmesinden ve ana sayfa bölümlerinden gizlemek istediğiniz kütüphaneleri seçin.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Appearance",
|
"title": "Appearance",
|
||||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Hide Remote Session Button",
|
"hide_remote_session_button": "Hide Remote Session Button",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Безпечна зона в елементах керування",
|
"safe_area_in_controls": "Безпечна зона в елементах керування",
|
||||||
"show_custom_menu_links": "Показати користувацькі посилання меню",
|
"show_custom_menu_links": "Показати користувацькі посилання меню",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Сховати медіатеки",
|
"hide_libraries": "Сховати медіатеки",
|
||||||
"select_libraries_you_want_to_hide": "Виберіть медіатеки, що бажаєте приховати з вкладки Медіатека і з секції на головній сторінці.",
|
"select_libraries_you_want_to_hide": "Виберіть медіатеки, що бажаєте приховати з вкладки Медіатека і з секції на головній сторінці.",
|
||||||
|
|||||||
@@ -137,7 +137,11 @@
|
|||||||
"appearance": {
|
"appearance": {
|
||||||
"title": "Appearance",
|
"title": "Appearance",
|
||||||
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
"merge_next_up_continue_watching": "Merge Continue Watching & Next Up",
|
||||||
|
"merge_next_up_continue_watching_hint": "Combine Continue Watching and Next Up into a single home row.",
|
||||||
|
"use_episode_images_next_up": "Use episode images for Next Up & Continue Watching",
|
||||||
|
"use_episode_images_next_up_hint": "Show each episode's own thumbnail in the Next Up and Continue Watching rows instead of the series image.",
|
||||||
"hide_remote_session_button": "Hide Remote Session Button",
|
"hide_remote_session_button": "Hide Remote Session Button",
|
||||||
|
"hide_remote_session_button_hint": "Hide the remote-sessions button from the home header.",
|
||||||
"show_home_backdrop": "Dynamic Home Backdrop",
|
"show_home_backdrop": "Dynamic Home Backdrop",
|
||||||
"show_hero_carousel": "Hero Carousel",
|
"show_hero_carousel": "Hero Carousel",
|
||||||
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
"show_series_poster_on_episode": "Show Series Poster on Episodes",
|
||||||
@@ -296,6 +300,7 @@
|
|||||||
},
|
},
|
||||||
"safe_area_in_controls": "Vùng an toàn trong điều khiển",
|
"safe_area_in_controls": "Vùng an toàn trong điều khiển",
|
||||||
"show_custom_menu_links": "Hiện liên kết tùy chỉnh",
|
"show_custom_menu_links": "Hiện liên kết tùy chỉnh",
|
||||||
|
"show_custom_menu_links_hint": "Show the custom links your server administrator added in the web config.",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Ẩn thư viện",
|
"hide_libraries": "Ẩn thư viện",
|
||||||
"select_libraries_you_want_to_hide": "Chọn các thư viện muốn ẩn khỏi mục Thư viện và Trang chủ.",
|
"select_libraries_you_want_to_hide": "Chọn các thư viện muốn ẩn khỏi mục Thư viện và Trang chủ.",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user