From 9c533e3c527b0ad2b09e5c4c288a6a2c8ace9f7a Mon Sep 17 00:00:00 2001 From: tapframe Date: Thu, 4 Sep 2025 18:00:37 +0530 Subject: [PATCH] some UI changes for ContinueWatching --- app.json | 6 ++-- src/components/home/ThisWeekSection.tsx | 38 ++++++++++++++------ src/components/player/AndroidVideoPlayer.tsx | 16 ++++++--- src/components/player/VideoPlayer.tsx | 10 ++++-- src/hooks/useSettings.ts | 2 +- src/screens/HomeScreen.tsx | 22 +++++++++--- src/screens/SettingsScreen.tsx | 2 +- src/screens/StreamsScreen.tsx | 10 ++++-- 8 files changed, 77 insertions(+), 29 deletions(-) diff --git a/app.json b/app.json index 41a07842..485a043d 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "Nuvio", "slug": "nuvio", - "version": "0.6.0-beta.7", + "version": "0.6.0-beta.8", "orientation": "default", "icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png", "userInterfaceStyle": "dark", @@ -16,7 +16,7 @@ "ios": { "supportsTablet": true, "icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png", - "buildNumber": "7", + "buildNumber": "8", "infoPlist": { "NSAppTransportSecurity": { "NSAllowsArbitraryLoads": true @@ -46,7 +46,7 @@ "WAKE_LOCK" ], "package": "com.nuvio.app", - "versionCode": 7, + "versionCode": 8, "architectures": [ "arm64-v8a", "armeabi-v7a", diff --git a/src/components/home/ThisWeekSection.tsx b/src/components/home/ThisWeekSection.tsx index 2f7f619a..b880dbcd 100644 --- a/src/components/home/ThisWeekSection.tsx +++ b/src/components/home/ThisWeekSection.tsx @@ -23,9 +23,10 @@ import { parseISO, isThisWeek, format, isAfter, isBefore } from 'date-fns'; import Animated, { FadeIn } from 'react-native-reanimated'; import { useCalendarData } from '../../hooks/useCalendarData'; +// Compute base sizes; actual tablet sizes will be adjusted inside component for responsiveness const { width } = Dimensions.get('window'); -const ITEM_WIDTH = width * 0.75; // Reduced width for better spacing -const ITEM_HEIGHT = 180; // Compact height for cleaner design +const ITEM_WIDTH = width * 0.75; // phone default +const ITEM_HEIGHT = 180; // phone default interface ThisWeekEpisode { id: string; @@ -57,6 +58,12 @@ export const ThisWeekSection = React.memo(() => { const { currentTheme } = useTheme(); const { calendarData, loading } = useCalendarData(); + // Responsive sizing for tablets + const deviceWidth = Dimensions.get('window').width; + const isTablet = deviceWidth >= 768; + const computedItemWidth = useMemo(() => (isTablet ? Math.min(deviceWidth * 0.46, 560) : ITEM_WIDTH), [isTablet, deviceWidth]); + const computedItemHeight = useMemo(() => (isTablet ? 220 : ITEM_HEIGHT), [isTablet]); + const thisWeekEpisodes = useMemo(() => { const thisWeekSection = calendarData.find(section => section.title === 'This Week'); if (!thisWeekSection) return []; @@ -109,7 +116,7 @@ export const ThisWeekSection = React.memo(() => { item.poster); return ( - + { > {/* Content area */} - + {item.seriesName} - + {item.title} {item.overview && ( - + {item.overview} )} - + S{item.season}:E{item.episode} • - + {formattedDate} @@ -197,10 +204,19 @@ export const ThisWeekSection = React.memo(() => { renderItem={renderEpisodeItem} horizontal showsHorizontalScrollIndicator={false} - contentContainerStyle={styles.listContent} - snapToInterval={ITEM_WIDTH + 16} + contentContainerStyle={[styles.listContent, { paddingLeft: isTablet ? 24 : 16, paddingRight: isTablet ? 24 : 16 }]} + snapToInterval={computedItemWidth + 16} decelerationRate="fast" snapToAlignment="start" + initialNumToRender={isTablet ? 4 : 3} + windowSize={3} + maxToRenderPerBatch={3} + removeClippedSubviews + getItemLayout={(data, index) => { + const length = computedItemWidth + 16; + const offset = length * index; + return { length, offset, index }; + }} ItemSeparatorComponent={() => } /> diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index 13c099ce..715a7362 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -799,11 +799,15 @@ const AndroidVideoPlayer: React.FC = () => { // Navigate immediately without delay ScreenOrientation.unlockAsync().then(() => { - // On iOS, explicitly return to portrait to avoid sticking in landscape - if (Platform.OS === 'ios') { + // On tablets keep rotation unlocked; on phones, return to portrait + const { width: dw, height: dh } = Dimensions.get('window'); + const isTablet = Math.min(dw, dh) >= 768 || ((Platform as any).isPad === true); + if (!isTablet) { setTimeout(() => { ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP).catch(() => {}); }, 50); + } else { + ScreenOrientation.unlockAsync().catch(() => {}); } disableImmersiveMode(); @@ -815,11 +819,15 @@ const AndroidVideoPlayer: React.FC = () => { (navigation as any).navigate('Streams', { id, type, episodeId, fromPlayer: true }); } }).catch(() => { - // Fallback: still try to restore portrait then navigate - if (Platform.OS === 'ios') { + // Fallback: still try to restore portrait on phones then navigate + const { width: dw, height: dh } = Dimensions.get('window'); + const isTablet = Math.min(dw, dh) >= 768 || ((Platform as any).isPad === true); + if (!isTablet) { setTimeout(() => { ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP).catch(() => {}); }, 50); + } else { + ScreenOrientation.unlockAsync().catch(() => {}); } disableImmersiveMode(); diff --git a/src/components/player/VideoPlayer.tsx b/src/components/player/VideoPlayer.tsx index 2689a419..3d39177e 100644 --- a/src/components/player/VideoPlayer.tsx +++ b/src/components/player/VideoPlayer.tsx @@ -838,10 +838,16 @@ const VideoPlayer: React.FC = () => { logger.warn('[VideoPlayer] Failed to unlock orientation:', orientationError); } - // On iOS, explicitly return to portrait to avoid sticking in landscape + // On iOS tablets, keep rotation unlocked; on phones, return to portrait if (Platform.OS === 'ios') { + const { width: dw, height: dh } = Dimensions.get('window'); + const isTablet = (Platform as any).isPad === true || Math.min(dw, dh) >= 768; setTimeout(() => { - ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP).catch(() => {}); + if (isTablet) { + ScreenOrientation.unlockAsync().catch(() => {}); + } else { + ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP).catch(() => {}); + } }, 50); } diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index 2607a63d..7151eb62 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -100,7 +100,7 @@ export const DEFAULT_SETTINGS: AppSettings = { // Quality filtering defaults excludedQualities: [], // No qualities excluded by default // Playback behavior defaults - alwaysResume: false, + alwaysResume: true, // Theme defaults themeId: 'default', customThemes: [], diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 842cc1f1..904ec0a9 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -369,9 +369,16 @@ const HomeScreen = () => { StatusBar.setTranslucent(true); StatusBar.setBackgroundColor('transparent'); - // Ensure portrait when coming back to Home on all platforms + // Allow free rotation on tablets; lock portrait on phones try { - ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP); + const { width: dw, height: dh } = Dimensions.get('window'); + const smallestDimension = Math.min(dw, dh); + const isTablet = (Platform.OS === 'ios' ? (Platform as any).isPad === true : smallestDimension >= 768); + if (isTablet) { + ScreenOrientation.unlockAsync(); + } else { + ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP); + } } catch {} // For iOS specifically @@ -569,7 +576,6 @@ const HomeScreen = () => { // Normal flow when addons are present (featured moved to ListHeaderComponent) data.push({ type: 'thisWeek', key: 'thisWeek' }); - data.push({ type: 'continueWatching', key: 'continueWatching' }); // Only show a limited number of catalogs initially for performance const catalogsToShow = catalogs.slice(0, visibleCatalogCount); @@ -631,6 +637,12 @@ const HomeScreen = () => { const memoizedThisWeekSection = useMemo(() => , []); const memoizedContinueWatchingSection = useMemo(() => , []); + const memoizedHeader = useMemo(() => ( + <> + {showHeroSection ? memoizedFeaturedContent : null} + {memoizedContinueWatchingSection} + + ), [showHeroSection, memoizedFeaturedContent, memoizedContinueWatchingSection]); // Track scroll direction manually for reliable behavior across platforms const lastScrollYRef = useRef(0); const lastToggleRef = useRef(0); @@ -652,7 +664,7 @@ const HomeScreen = () => { case 'thisWeek': return wrapper(memoizedThisWeekSection); case 'continueWatching': - return wrapper(memoizedContinueWatchingSection); + return null; // Moved to ListHeaderComponent to avoid remounts on scroll case 'catalog': return wrapper(); case 'placeholder': @@ -749,7 +761,7 @@ const HomeScreen = () => { { paddingTop: insets.top } ]} showsVerticalScrollIndicator={false} - ListHeaderComponent={showHeroSection ? memoizedFeaturedContent : null} + ListHeaderComponent={memoizedHeader} ListFooterComponent={ListFooterComponent} onMomentumScrollEnd={handleScrollEnd} onEndReached={handleLoadMoreCatalogs} diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index f9f4dc7e..69783346 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -582,7 +582,7 @@ const SettingsScreen: React.FC = () => { /> { logger.warn('[StreamsScreen] MKV support detection failed:', e); } - // Add pre-navigation orientation lock to reduce glitch + // Add pre-navigation orientation lock to reduce glitch on phones only; tablets can rotate freely try { - await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE); + const { width: dw, height: dh } = Dimensions.get('window'); + const isTablet = Math.min(dw, dh) >= 768 || ((Platform as any).isPad === true); + if (!isTablet) { + await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE); + } else { + await ScreenOrientation.unlockAsync(); + } } catch (e) { logger.warn('[StreamsScreen] Pre-navigation orientation lock failed:', e); }