From 599096f88317ba34428fe75ed2daafc7b0ce9593 Mon Sep 17 00:00:00 2001 From: Gauvino Date: Fri, 12 Jun 2026 16:23:08 +0200 Subject: [PATCH] fix(review): address second CodeRabbit pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - streamystats: derive toggle enablement from the same effective URL the input renders (locked admin URL no longer disables every switch) - FilterSheet: use the deep-equality rule for toggling that rendering already uses — option objects are recreated across renders - DownloadCard: take t from useTranslation so badge labels re-render on language change - fileOperations: count trickplay bytes in the storage total, matching the per-item size model - PendingAccountSaveModal: warn instead of silently swallowing a failed account save --- .../(home)/settings/plugins/streamystats/page.tsx | 11 +++++++---- components/PendingAccountSaveModal.tsx | 2 +- components/downloads/DownloadCard.tsx | 3 ++- components/filters/FilterSheet.tsx | 10 +++++++--- providers/Downloads/fileOperations.ts | 7 +++++-- 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx b/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx index b0383468..e78c3781 100644 --- a/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx @@ -58,7 +58,12 @@ export default function StreamystatsPage() { pluginSettings?.streamyStatsPromotedWatchlists?.locked === true; const hideWatchlistsTabLocked = pluginSettings?.hideWatchlistsTab?.locked === true; - const isStreamystatsEnabled = !!url; + // 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 cleanUrl = url.endsWith("/") ? url.slice(0, -1) : url; @@ -155,9 +160,7 @@ export default function StreamystatsPage() { placeholder={t( "home.settings.plugins.streamystats.server_url_placeholder", )} - value={ - isUrlLocked ? (settings?.streamyStatsServerUrl ?? "") : url - } + value={effectiveUrl} keyboardType='url' returnKeyType='done' autoCapitalize='none' diff --git a/components/PendingAccountSaveModal.tsx b/components/PendingAccountSaveModal.tsx index 95d68a6e..9c2def07 100644 --- a/components/PendingAccountSaveModal.tsx +++ b/components/PendingAccountSaveModal.tsx @@ -37,7 +37,7 @@ export const PendingAccountSaveModal: React.FC = () => { const serverName = pending?.serverName; setPending(null); saveCurrentAccount({ securityType, pinCode, serverName }).catch( - () => {}, + (error) => console.warn("Failed to save account:", error), ); }} /> diff --git a/components/downloads/DownloadCard.tsx b/components/downloads/DownloadCard.tsx index da5263c4..2d1b85a0 100644 --- a/components/downloads/DownloadCard.tsx +++ b/components/downloads/DownloadCard.tsx @@ -1,7 +1,7 @@ import { Ionicons } from "@expo/vector-icons"; import { Image } from "expo-image"; -import { t } from "i18next"; import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; import { ActivityIndicator, TouchableOpacity, @@ -35,6 +35,7 @@ interface DownloadCardProps extends TouchableOpacityProps { } export const DownloadCard = ({ process, ...props }: DownloadCardProps) => { + const { t } = useTranslation(); const { cancelDownload } = useDownload(); const router = useRouter(); const queryClient = useNetworkAwareQueryClient(); diff --git a/components/filters/FilterSheet.tsx b/components/filters/FilterSheet.tsx index fd2e4f4d..8f87ac22 100644 --- a/components/filters/FilterSheet.tsx +++ b/components/filters/FilterSheet.tsx @@ -193,15 +193,19 @@ export const FilterSheet = ({ { + // Match the deep-equality rule used to render the selected + // state below — option objects are recreated across renders, + // so reference checks would re-add an already selected item. + const isSelected = values.some((value) => isEqual(value, item)); if (multiple) { - if (!values.includes(item)) set(values.concat(item)); - else set(values.filter((v) => v !== item)); + if (!isSelected) set(values.concat(item)); + else set(values.filter((value) => !isEqual(value, item))); setTimeout(() => { setOpen(false); }, 250); } else { - if (!values.includes(item)) { + if (!isSelected) { set([item]); setTimeout(() => { setOpen(false); diff --git a/providers/Downloads/fileOperations.ts b/providers/Downloads/fileOperations.ts index 6a038740..05240998 100644 --- a/providers/Downloads/fileOperations.ts +++ b/providers/Downloads/fileOperations.ts @@ -97,6 +97,9 @@ export function getDownloadedItemSize(id: string): number { export function calculateTotalDownloadedSize(): number { const items = getAllDownloadedItems(); return items.reduce((sum, item) => { + // Trickplay bytes count too — getDownloadedItemSize models per-item size + // as video + trickplay, the total must match. + const trickplaySize = item.trickPlayData?.size ?? 0; // Read the live file size on disk so the total reflects actual usage and // self-heals items whose stored videoFileSize is 0 (old schema, or // `fileInfo.size` was undefined at download time). Fall back to the stored @@ -105,12 +108,12 @@ export function calculateTotalDownloadedSize(): number { try { const file = new File(filePathToUri(item.videoFilePath)); if (file.exists) { - return sum + (file.size ?? item.videoFileSize ?? 0); + return sum + (file.size ?? item.videoFileSize ?? 0) + trickplaySize; } } catch (error) { console.warn("Failed to stat downloaded file for size:", error); } } - return sum + (item.videoFileSize ?? 0); + return sum + (item.videoFileSize ?? 0) + trickplaySize; }, 0); }