From a85e57718a1d24aa462e55c414796ca4a0755b3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 22:03:16 +0000 Subject: [PATCH 1/4] fix: provide overrideItemLayout to FlashList in list mode to prevent random spacing Agent-Logs-Url: https://github.com/Kallb123/SimpleMediaBrowser/sessions/b35a7100-8b81-45f0-8ebe-5564b7b2ebb3 Co-authored-by: Kallb123 <1054734+Kallb123@users.noreply.github.com> --- src/components/MediaBrowserScreen.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/MediaBrowserScreen.tsx b/src/components/MediaBrowserScreen.tsx index fbb1d65..5a770f9 100644 --- a/src/components/MediaBrowserScreen.tsx +++ b/src/components/MediaBrowserScreen.tsx @@ -598,6 +598,7 @@ export function MediaBrowserScreen({ mediaFilter }: MediaBrowserScreenProps) { keyExtractor={(item: DisplayItem) => item.key} numColumns={numColumns} extraData={`${editMode}|${pressedKey ?? ''}|${isListMode}|${Array.from(selectedShows).join(',')}`} + overrideItemLayout={isListMode ? (layout) => { layout.size = listRowHeight; } : undefined} renderItem={({ item }: { item: DisplayItem }) => { const isFolder = item.kind === 'folder'; const isEditable = ( From df4b44ee3ee517ff5f2c95cd40579139cfb287a4 Mon Sep 17 00:00:00 2001 From: Kallum Burgin <1054734+Kallb123@users.noreply.github.com> Date: Wed, 6 May 2026 23:13:07 +0100 Subject: [PATCH 2/4] Bump version to 1.6.2 and versionCode to 32 --- app.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app.json b/app.json index 423ce54..be7ecfc 100644 --- a/app.json +++ b/app.json @@ -3,7 +3,7 @@ "name": "SimpleMediaBrowser", "owner": "kallb123", "slug": "SimpleMediaBrowser", - "version": "1.6.1", + "version": "1.6.2", "orientation": "default", "icon": "./assets/images/icon.png", "scheme": "simplemediabrowser", @@ -20,7 +20,7 @@ }, "predictiveBackGestureEnabled": false, "package": "uk.kallum.SimpleMediaBrowser", - "versionCode": 31 + "versionCode": 32 }, "web": { "output": "static", From 8e7a0caa105c2973dcf515e1dde8b3f7c1cec54f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 22:26:20 +0000 Subject: [PATCH 3/4] fix: use FlatList with getItemLayout for list mode to eliminate spacing issue Agent-Logs-Url: https://github.com/Kallb123/SimpleMediaBrowser/sessions/a6a3a153-4043-4a4d-a2a5-61548732111e Co-authored-by: Kallb123 <1054734+Kallb123@users.noreply.github.com> --- src/components/MediaBrowserScreen.tsx | 177 +++++++++++++++----------- 1 file changed, 103 insertions(+), 74 deletions(-) diff --git a/src/components/MediaBrowserScreen.tsx b/src/components/MediaBrowserScreen.tsx index 5a770f9..a232ac5 100644 --- a/src/components/MediaBrowserScreen.tsx +++ b/src/components/MediaBrowserScreen.tsx @@ -1,4 +1,4 @@ -import { BackHandler, StyleSheet, TouchableOpacity, View, useWindowDimensions } from 'react-native'; +import { BackHandler, FlatList, StyleSheet, TouchableOpacity, View, useWindowDimensions } from 'react-native'; import { openMediaInExternalApp } from '@/scripts/openMedia'; import { ThemedText } from '@/components/ThemedText'; import { ThemedView } from '@/components/ThemedView'; @@ -593,44 +593,43 @@ export function MediaBrowserScreen({ mediaFilter }: MediaBrowserScreenProps) { )} - item.key} - numColumns={numColumns} - extraData={`${editMode}|${pressedKey ?? ''}|${isListMode}|${Array.from(selectedShows).join(',')}`} - overrideItemLayout={isListMode ? (layout) => { layout.size = listRowHeight; } : undefined} - renderItem={({ item }: { item: DisplayItem }) => { - const isFolder = item.kind === 'folder'; - const isEditable = ( - item.mediaType === 'show' || - item.mediaType === 'movie' || - item.mediaType === 'episode' - ); - // Shows at root level get tap-to-select behaviour in edit mode. - const isShowAtRoot = item.kind === 'folder' && item.mediaType === 'show' && navStack.length === 0; - const isSelected = editMode && isShowAtRoot && selectedShows.has(item.key); - - // Folders always navigate; files always open in player. - let handlePress: () => void; - if (item.kind === 'folder') { - handlePress = item.onPress; - } else { - handlePress = () => { - logger.log('MediaBrowserScreen', `Opening file: ${item.mediaObject.filename} (${item.mediaObject.path})`); - openMediaInExternalApp(item.mediaObject.path, item.mediaObject.filename).catch((e: unknown) => { - logger.error('MediaBrowserScreen', `Failed to open file: ${item.mediaObject.path}`, e); - }); - }; - } - - // Edit badge callback – lets the user navigate to the edit screen for this item. - const onEditPress = editMode && isEditable ? () => openEditScreen(item) : undefined; - - if (isListMode) { + {isListMode ? ( + item.key} + extraData={`${editMode}|${Array.from(selectedShows).join(',')}`} + getItemLayout={(_data, index) => ({ + length: listRowHeight, + offset: listRowHeight * index, + index, + })} + renderItem={({ item }: { item: DisplayItem }) => { + const isEditable = ( + item.mediaType === 'show' || + item.mediaType === 'movie' || + item.mediaType === 'episode' + ); + const isShowAtRoot = item.kind === 'folder' && item.mediaType === 'show' && navStack.length === 0; + const isSelected = editMode && isShowAtRoot && selectedShows.has(item.key); + + let handlePress: () => void; + if (item.kind === 'folder') { + handlePress = item.onPress; + } else { + handlePress = () => { + logger.log('MediaBrowserScreen', `Opening file: ${item.mediaObject.filename} (${item.mediaObject.path})`); + openMediaInExternalApp(item.mediaObject.path, item.mediaObject.filename).catch((e: unknown) => { + logger.error('MediaBrowserScreen', `Failed to open file: ${item.mediaObject.path}`, e); + }); + }; + } + + const onEditPress = editMode && isEditable ? () => openEditScreen(item) : undefined; // In edit mode, long press on a show at root selects it for merging. const handleLongPress = editMode && isShowAtRoot ? () => toggleShowSelection(item.key) : undefined; + return ( ); - } - - // Poster mode - const hasPoster = !!item.posterUri; - const hasBothImages = hasPoster && !!item.thumbnailUri; - const isRevealed = pressedKey === item.key; - - // In edit mode: long press on a show at root selects it for merging; - // thumbnail reveal is disabled. Outside edit mode: long press reveals - // the video thumbnail when a poster is also available. - const handleLongPress = editMode - ? (isShowAtRoot ? () => toggleShowSelection(item.key) : undefined) - : hasBothImages - ? () => setPressedKey(item.key) - : undefined; - const handlePressOut = editMode - ? undefined - : hasBothImages - ? () => setPressedKey(null) - : undefined; + }} + contentContainerStyle={styles.listContent} + /> + ) : ( + item.key} + numColumns={numColumns} + extraData={`${editMode}|${pressedKey ?? ''}|${Array.from(selectedShows).join(',')}`} + renderItem={({ item }: { item: DisplayItem }) => { + const isEditable = ( + item.mediaType === 'show' || + item.mediaType === 'movie' || + item.mediaType === 'episode' + ); + const isShowAtRoot = item.kind === 'folder' && item.mediaType === 'show' && navStack.length === 0; + const isSelected = editMode && isShowAtRoot && selectedShows.has(item.key); + + let handlePress: () => void; + if (item.kind === 'folder') { + handlePress = item.onPress; + } else { + handlePress = () => { + logger.log('MediaBrowserScreen', `Opening file: ${item.mediaObject.filename} (${item.mediaObject.path})`); + openMediaInExternalApp(item.mediaObject.path, item.mediaObject.filename).catch((e: unknown) => { + logger.error('MediaBrowserScreen', `Failed to open file: ${item.mediaObject.path}`, e); + }); + }; + } + + const onEditPress = editMode && isEditable ? () => openEditScreen(item) : undefined; + + const hasPoster = !!item.posterUri; + const hasBothImages = hasPoster && !!item.thumbnailUri; + const isRevealed = pressedKey === item.key; + + // In edit mode: long press on a show at root selects it for merging; + // thumbnail reveal is disabled. Outside edit mode: long press reveals + // the video thumbnail when a poster is also available. + const handleLongPress = editMode + ? (isShowAtRoot ? () => toggleShowSelection(item.key) : undefined) + : hasBothImages + ? () => setPressedKey(item.key) + : undefined; + const handlePressOut = editMode + ? undefined + : hasBothImages + ? () => setPressedKey(null) + : undefined; - return ( - - ); - }} - contentContainerStyle={isListMode ? styles.listContent : styles.gridContent} - /> + return ( + + ); + }} + contentContainerStyle={styles.gridContent} + /> + )} {/* Merge toolbar – visible when 2+ shows are selected in edit mode at root level */} {editMode && selectedShows.size >= 2 && navStack.length === 0 && ( From 9b702a7cd3746663cf56a27136ce577862029229 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 22:28:04 +0000 Subject: [PATCH 4/4] refactor: extract shared getItemHandlers helper to reduce renderItem duplication Agent-Logs-Url: https://github.com/Kallb123/SimpleMediaBrowser/sessions/a6a3a153-4043-4a4d-a2a5-61548732111e Co-authored-by: Kallb123 <1054734+Kallb123@users.noreply.github.com> --- src/components/MediaBrowserScreen.tsx | 75 ++++++++++++--------------- 1 file changed, 33 insertions(+), 42 deletions(-) diff --git a/src/components/MediaBrowserScreen.tsx b/src/components/MediaBrowserScreen.tsx index a232ac5..9802577 100644 --- a/src/components/MediaBrowserScreen.tsx +++ b/src/components/MediaBrowserScreen.tsx @@ -532,6 +532,35 @@ export function MediaBrowserScreen({ mediaFilter }: MediaBrowserScreenProps) { }); }, []); + /** + * Computes the common item handlers shared by both the list-mode (FlatList) + * and grid-mode (FlashList) renderItem callbacks. + */ + const getItemHandlers = useCallback((item: DisplayItem) => { + const isEditable = ( + item.mediaType === 'show' || + item.mediaType === 'movie' || + item.mediaType === 'episode' + ); + const isShowAtRoot = item.kind === 'folder' && item.mediaType === 'show' && navStack.length === 0; + const isSelected = editMode && isShowAtRoot && selectedShows.has(item.key); + + let handlePress: () => void; + if (item.kind === 'folder') { + handlePress = item.onPress; + } else { + handlePress = () => { + logger.log('MediaBrowserScreen', `Opening file: ${item.mediaObject.filename} (${item.mediaObject.path})`); + openMediaInExternalApp(item.mediaObject.path, item.mediaObject.filename).catch((e: unknown) => { + logger.error('MediaBrowserScreen', `Failed to open file: ${item.mediaObject.path}`, e); + }); + }; + } + + const onEditPress = editMode && isEditable ? () => openEditScreen(item) : undefined; + return { isEditable, isShowAtRoot, isSelected, handlePress, onEditPress }; + }, [editMode, navStack, selectedShows, openEditScreen]); + return ( {mediaSources.length > 0 && hasLibraryContent ? ( @@ -597,6 +626,8 @@ export function MediaBrowserScreen({ mediaFilter }: MediaBrowserScreenProps) { item.key} + // pressedKey is a poster-mode-only feature (thumbnail reveal), so it is + // intentionally excluded from list-mode extraData. extraData={`${editMode}|${Array.from(selectedShows).join(',')}`} getItemLayout={(_data, index) => ({ length: listRowHeight, @@ -604,27 +635,7 @@ export function MediaBrowserScreen({ mediaFilter }: MediaBrowserScreenProps) { index, })} renderItem={({ item }: { item: DisplayItem }) => { - const isEditable = ( - item.mediaType === 'show' || - item.mediaType === 'movie' || - item.mediaType === 'episode' - ); - const isShowAtRoot = item.kind === 'folder' && item.mediaType === 'show' && navStack.length === 0; - const isSelected = editMode && isShowAtRoot && selectedShows.has(item.key); - - let handlePress: () => void; - if (item.kind === 'folder') { - handlePress = item.onPress; - } else { - handlePress = () => { - logger.log('MediaBrowserScreen', `Opening file: ${item.mediaObject.filename} (${item.mediaObject.path})`); - openMediaInExternalApp(item.mediaObject.path, item.mediaObject.filename).catch((e: unknown) => { - logger.error('MediaBrowserScreen', `Failed to open file: ${item.mediaObject.path}`, e); - }); - }; - } - - const onEditPress = editMode && isEditable ? () => openEditScreen(item) : undefined; + const { isEditable, isShowAtRoot, isSelected, handlePress, onEditPress } = getItemHandlers(item); // In edit mode, long press on a show at root selects it for merging. const handleLongPress = editMode && isShowAtRoot ? () => toggleShowSelection(item.key) @@ -654,27 +665,7 @@ export function MediaBrowserScreen({ mediaFilter }: MediaBrowserScreenProps) { numColumns={numColumns} extraData={`${editMode}|${pressedKey ?? ''}|${Array.from(selectedShows).join(',')}`} renderItem={({ item }: { item: DisplayItem }) => { - const isEditable = ( - item.mediaType === 'show' || - item.mediaType === 'movie' || - item.mediaType === 'episode' - ); - const isShowAtRoot = item.kind === 'folder' && item.mediaType === 'show' && navStack.length === 0; - const isSelected = editMode && isShowAtRoot && selectedShows.has(item.key); - - let handlePress: () => void; - if (item.kind === 'folder') { - handlePress = item.onPress; - } else { - handlePress = () => { - logger.log('MediaBrowserScreen', `Opening file: ${item.mediaObject.filename} (${item.mediaObject.path})`); - openMediaInExternalApp(item.mediaObject.path, item.mediaObject.filename).catch((e: unknown) => { - logger.error('MediaBrowserScreen', `Failed to open file: ${item.mediaObject.path}`, e); - }); - }; - } - - const onEditPress = editMode && isEditable ? () => openEditScreen(item) : undefined; + const { isEditable, isShowAtRoot, isSelected, handlePress, onEditPress } = getItemHandlers(item); const hasPoster = !!item.posterUri; const hasBothImages = hasPoster && !!item.thumbnailUri;