mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
some UI changes for ContinueWatching
This commit is contained in:
parent
afaca6467f
commit
9c533e3c52
8 changed files with 77 additions and 29 deletions
6
app.json
6
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",
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<View style={styles.episodeItemContainer}>
|
||||
<View style={[styles.episodeItemContainer, { width: computedItemWidth, height: computedItemHeight }]}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.episodeItem,
|
||||
|
|
@ -143,30 +150,30 @@ export const ThisWeekSection = React.memo(() => {
|
|||
>
|
||||
{/* Content area */}
|
||||
<View style={styles.contentArea}>
|
||||
<Text style={[styles.seriesName, { color: currentTheme.colors.white }]} numberOfLines={1}>
|
||||
<Text style={[styles.seriesName, { color: currentTheme.colors.white, fontSize: isTablet ? 18 : undefined }]} numberOfLines={1}>
|
||||
{item.seriesName}
|
||||
</Text>
|
||||
|
||||
<Text style={[styles.episodeTitle, { color: 'rgba(255,255,255,0.9)' }]} numberOfLines={2}>
|
||||
<Text style={[styles.episodeTitle, { color: 'rgba(255,255,255,0.9)', fontSize: isTablet ? 16 : undefined }]} numberOfLines={2}>
|
||||
{item.title}
|
||||
</Text>
|
||||
|
||||
{item.overview && (
|
||||
<Text style={[styles.overview, { color: 'rgba(255,255,255,0.8)' }]} numberOfLines={2}>
|
||||
<Text style={[styles.overview, { color: 'rgba(255,255,255,0.8)', fontSize: isTablet ? 13 : undefined }]} numberOfLines={isTablet ? 3 : 2}>
|
||||
{item.overview}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<View style={styles.dateContainer}>
|
||||
<Text style={[styles.episodeInfo, { color: 'rgba(255,255,255,0.7)' }]}>
|
||||
<Text style={[styles.episodeInfo, { color: 'rgba(255,255,255,0.7)', fontSize: isTablet ? 13 : undefined }]}>
|
||||
S{item.season}:E{item.episode} •
|
||||
</Text>
|
||||
<MaterialIcons
|
||||
name="event"
|
||||
size={14}
|
||||
size={isTablet ? 16 : 14}
|
||||
color={currentTheme.colors.primary}
|
||||
/>
|
||||
<Text style={[styles.releaseDate, { color: currentTheme.colors.primary }]}>
|
||||
<Text style={[styles.releaseDate, { color: currentTheme.colors.primary, fontSize: isTablet ? 14 : undefined }]}>
|
||||
{formattedDate}
|
||||
</Text>
|
||||
</View>
|
||||
|
|
@ -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={() => <View style={{ width: 16 }} />}
|
||||
/>
|
||||
</Animated.View>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
|
|
|
|||
|
|
@ -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(() => <ThisWeekSection />, []);
|
||||
const memoizedContinueWatchingSection = useMemo(() => <ContinueWatchingSection ref={continueWatchingRef} />, []);
|
||||
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(<CatalogSection catalog={item.catalog} />);
|
||||
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}
|
||||
|
|
|
|||
|
|
@ -582,7 +582,7 @@ const SettingsScreen: React.FC = () => {
|
|||
/>
|
||||
<SettingItem
|
||||
title="Version"
|
||||
description="0.6.0-beta.7"
|
||||
description="0.6.0-beta.8"
|
||||
icon="info-outline"
|
||||
isLast={true}
|
||||
isTablet={isTablet}
|
||||
|
|
|
|||
|
|
@ -940,9 +940,15 @@ export const StreamsScreen = () => {
|
|||
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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue