Skip to content

Commit 52a2016

Browse files
committed
Add landscape support
1 parent d34e6cb commit 52a2016

File tree

9 files changed

+335
-178
lines changed

9 files changed

+335
-178
lines changed

example/app.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"name": "example",
44
"slug": "example",
55
"version": "1.0.0",
6-
"orientation": "portrait",
6+
"orientation": "default",
77
"icon": "./assets/icon.png",
88
"userInterfaceStyle": "light",
99
"splash": {

src/widgets/video/layer/CLDVideoLayer.tsx

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import React from 'react';
2-
import { View, TouchableOpacity, Text, PanResponder, ActivityIndicator, Animated, StyleSheet, Easing } from 'react-native';
2+
import { View, TouchableOpacity, Text, PanResponder, ActivityIndicator, Animated, StyleSheet, Easing, Dimensions } from 'react-native';
33
import { AVPlaybackStatusSuccess } from 'expo-av';
44
import { Ionicons } from '@expo/vector-icons';
55
import AdvancedVideo from '../../../AdvancedVideo';
66
import { CLDVideoLayerProps, ButtonPosition } from './types';
77
import { formatTime, handleDefaultShare } from './utils';
8-
import { styles } from './styles';
8+
import { styles, getResponsiveStyles } from './styles';
99
import { TopControls, CenterControls, BottomControls } from './components';
1010
import { ICON_SIZES } from './constants';
1111

@@ -16,6 +16,7 @@ interface CLDVideoLayerState {
1616
seekingPosition: number;
1717
lastSeekPosition: number;
1818
isSeekingComplete: boolean;
19+
isLandscape: boolean;
1920
}
2021

2122
export class CLDVideoLayer extends React.Component<CLDVideoLayerProps, CLDVideoLayerState> {
@@ -24,20 +25,28 @@ export class CLDVideoLayer extends React.Component<CLDVideoLayerProps, CLDVideoL
2425
private fadeAnim: Animated.Value;
2526
private autoHideTimeoutId: NodeJS.Timeout | null = null;
2627
private panResponder: any;
28+
private orientationSubscription: any = null;
29+
private orientationCheckInterval: NodeJS.Timeout | null = null;
2730

2831
constructor(props: CLDVideoLayerProps) {
2932
super(props);
3033
this.videoRef = React.createRef<AdvancedVideo>();
3134
this.seekbarRef = React.createRef<View>();
3235
this.fadeAnim = new Animated.Value(1);
3336

37+
// Get initial orientation
38+
const { width, height } = Dimensions.get('window');
39+
const initialIsLandscape = width > height;
40+
console.log('🏗️ Initial orientation setup:', { width, height, initialIsLandscape });
41+
3442
this.state = {
3543
status: null,
3644
isControlsVisible: true,
3745
isSeeking: false,
3846
seekingPosition: 0,
3947
lastSeekPosition: 0,
4048
isSeekingComplete: false,
49+
isLandscape: initialIsLandscape,
4150
};
4251

4352
this.panResponder = PanResponder.create({
@@ -114,12 +123,56 @@ export class CLDVideoLayer extends React.Component<CLDVideoLayerProps, CLDVideoL
114123
if (this.state.isControlsVisible) {
115124
this.startAutoHideTimer();
116125
}
126+
127+
// Listen for orientation changes
128+
console.log('🚀 Setting up orientation listener. Initial state:', this.state.isLandscape);
129+
130+
// Try multiple approaches for orientation detection
131+
this.orientationSubscription = Dimensions.addEventListener('change', this.handleOrientationChange);
132+
133+
// Also check orientation periodically as fallback
134+
this.orientationCheckInterval = setInterval(() => {
135+
const { width, height } = Dimensions.get('window');
136+
const isLandscape = width > height;
137+
if (isLandscape !== this.state.isLandscape) {
138+
console.log('🔄 Orientation detected via polling:', { width, height, isLandscape });
139+
this.setState({ isLandscape });
140+
}
141+
}, 500);
142+
143+
console.log('✅ Orientation listener registered');
117144
}
118145

119146
componentWillUnmount() {
120147
this.clearAutoHideTimer();
148+
149+
// Remove orientation listener
150+
if (this.orientationSubscription && this.orientationSubscription.remove) {
151+
this.orientationSubscription.remove();
152+
}
153+
154+
// Clear orientation polling interval
155+
if (this.orientationCheckInterval) {
156+
clearInterval(this.orientationCheckInterval);
157+
this.orientationCheckInterval = null;
158+
}
121159
}
122160

161+
handleOrientationChange = ({ window }: any) => {
162+
const { width, height } = window;
163+
const isLandscape = width > height;
164+
console.log('🔄 Orientation change detected:', {
165+
width,
166+
height,
167+
isLandscape,
168+
currentState: this.state.isLandscape
169+
});
170+
if (isLandscape !== this.state.isLandscape) {
171+
console.log('📱 Updating orientation state to:', isLandscape);
172+
this.setState({ isLandscape });
173+
}
174+
};
175+
123176
clearAutoHideTimer = () => {
124177
if (this.autoHideTimeoutId) {
125178
clearTimeout(this.autoHideTimeoutId);
@@ -247,11 +300,17 @@ export class CLDVideoLayer extends React.Component<CLDVideoLayerProps, CLDVideoL
247300

248301
render() {
249302
const { cldVideo, videoUrl, onBack, backButtonPosition, shareButtonPosition, showCenterPlayButton = true } = this.props;
250-
const { status } = this.state;
303+
const { status, isLandscape } = this.state;
251304
const progress = this.getProgress();
252305
const currentPosition = this.getCurrentPosition();
253306
const isVideoLoaded = status?.isLoaded === true;
254307

308+
// Get responsive styles based on current orientation
309+
const responsiveStyles = getResponsiveStyles(isLandscape);
310+
311+
// Debug log for render
312+
console.log('🎨 Rendering with isLandscape:', isLandscape);
313+
255314
return (
256315
<TouchableOpacity
257316
activeOpacity={1}
@@ -283,6 +342,7 @@ export class CLDVideoLayer extends React.Component<CLDVideoLayerProps, CLDVideoL
283342
onShare={this.handleShare}
284343
backButtonPosition={backButtonPosition}
285344
shareButtonPosition={shareButtonPosition}
345+
isLandscape={isLandscape}
286346
/>
287347
{showCenterPlayButton && (
288348
<CenterControls status={status} onPlayPause={this.handlePlayPause} />
@@ -298,6 +358,7 @@ export class CLDVideoLayer extends React.Component<CLDVideoLayerProps, CLDVideoL
298358
panResponder={this.panResponder}
299359
backButtonPosition={backButtonPosition}
300360
shareButtonPosition={shareButtonPosition}
361+
isLandscape={isLandscape}
301362
/>
302363
</Animated.View>
303364

@@ -306,15 +367,15 @@ export class CLDVideoLayer extends React.Component<CLDVideoLayerProps, CLDVideoL
306367
<>
307368
{onBack && backButtonPosition === ButtonPosition.SE && (
308369
<TouchableOpacity
309-
style={[styles.topButton, styles.buttonPositionSE]}
370+
style={[responsiveStyles.topButton, responsiveStyles.buttonPositionSE]}
310371
onPress={onBack}
311372
>
312373
<Ionicons name="close" size={ICON_SIZES.top} color="white" />
313374
</TouchableOpacity>
314375
)}
315376
{shareButtonPosition === ButtonPosition.SE && (
316377
<TouchableOpacity
317-
style={[styles.topButton, styles.buttonPositionSE]}
378+
style={[responsiveStyles.topButton, responsiveStyles.buttonPositionSE]}
318379
onPress={this.handleShare}
319380
>
320381
<Ionicons name="share-outline" size={ICON_SIZES.top} color="white" />

src/widgets/video/layer/components/BottomControls.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import { View, TouchableOpacity } from 'react-native';
33
import { Ionicons } from '@expo/vector-icons';
44
import { BottomControlsProps, ButtonPosition } from '../types';
5-
import { styles } from '../styles';
5+
import { styles, getResponsiveStyles } from '../styles';
66
import { ICON_SIZES } from '../constants';
77
import { Seekbar } from './Seekbar';
88

@@ -17,7 +17,9 @@ export const BottomControls: React.FC<BottomControlsProps> = ({
1717
panResponder,
1818
backButtonPosition,
1919
shareButtonPosition,
20+
isLandscape = false,
2021
}) => {
22+
const responsiveStyles = getResponsiveStyles(isLandscape);
2123
const progress = getProgress();
2224
const currentPosition = getCurrentPosition();
2325

@@ -26,18 +28,19 @@ export const BottomControls: React.FC<BottomControlsProps> = ({
2628

2729
return (
2830
<View style={[
29-
styles.bottomControlsBar,
31+
responsiveStyles.bottomControlsBar,
3032
hasSEButton && { paddingRight: 80 } // Create space for SE button
3133
]}>
32-
<View style={styles.bottomLeftControls}>
34+
<View style={responsiveStyles.bottomLeftControls}>
3335
<TouchableOpacity
34-
style={styles.playPauseButton}
36+
style={responsiveStyles.playPauseButton}
3537
onPress={onPlayPause}
3638
>
3739
<Ionicons
3840
name={status?.isPlaying ? 'pause' : 'play'}
3941
size={ICON_SIZES.bottomPlayPause}
4042
color="white"
43+
style={responsiveStyles.playPauseIcon}
4144
/>
4245
</TouchableOpacity>
4346

@@ -48,18 +51,20 @@ export const BottomControls: React.FC<BottomControlsProps> = ({
4851
formatTime={formatTime}
4952
seekbarRef={seekbarRef}
5053
panResponder={panResponder}
54+
isLandscape={isLandscape}
5155
/>
5256
</View>
5357

54-
<View style={styles.bottomRightControls}>
58+
<View style={responsiveStyles.bottomRightControls}>
5559
<TouchableOpacity
56-
style={styles.volumeButton}
60+
style={responsiveStyles.volumeButton}
5761
onPress={onMuteToggle}
5862
>
5963
<Ionicons
6064
name={status?.isMuted ? 'volume-mute' : 'volume-high'}
6165
size={ICON_SIZES.bottomVolume}
6266
color="white"
67+
style={responsiveStyles.volumeIcon}
6368
/>
6469
</TouchableOpacity>
6570
</View>

src/widgets/video/layer/components/Seekbar.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import { View, Text } from 'react-native';
33
import { SeekbarProps } from '../types';
4-
import { styles } from '../styles';
4+
import { styles, getResponsiveStyles } from '../styles';
55

66
export const Seekbar: React.FC<SeekbarProps> = ({
77
progress,
@@ -10,24 +10,27 @@ export const Seekbar: React.FC<SeekbarProps> = ({
1010
formatTime,
1111
seekbarRef,
1212
panResponder,
13+
isLandscape = false,
1314
}) => {
15+
const responsiveStyles = getResponsiveStyles(isLandscape);
16+
1417
return (
15-
<View style={styles.seekbarContainer}>
18+
<View style={responsiveStyles.seekbarContainer}>
1619
<View
1720
ref={seekbarRef}
18-
style={styles.seekbar}
21+
style={responsiveStyles.seekbar}
1922
{...panResponder.panHandlers}
2023
>
21-
<View style={styles.seekbarTrack} />
24+
<View style={responsiveStyles.seekbarTrack} />
2225
<View
2326
style={[
24-
styles.seekbarProgress,
27+
responsiveStyles.seekbarProgress,
2528
{ width: `${progress * 100}%` }
2629
]}
2730
/>
2831
<View
2932
style={[
30-
styles.seekbarHandle,
33+
responsiveStyles.seekbarHandle,
3134
{ left: `${progress * 100}%` }
3235
]}
3336
/>

src/widgets/video/layer/components/TopControls.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,26 @@ import React from 'react';
22
import { View, TouchableOpacity } from 'react-native';
33
import { Ionicons } from '@expo/vector-icons';
44
import { TopControlsProps, ButtonPosition } from '../types';
5-
import { styles } from '../styles';
5+
import { styles, getResponsiveStyles } from '../styles';
66
import { ICON_SIZES } from '../constants';
77

88
export const TopControls: React.FC<TopControlsProps> = ({
99
onBack,
1010
onShare,
1111
backButtonPosition,
12-
shareButtonPosition
12+
shareButtonPosition,
13+
isLandscape = false
1314
}) => {
15+
const responsiveStyles = getResponsiveStyles(isLandscape);
16+
1417
const getPositionStyle = (position: ButtonPosition) => {
1518
switch (position) {
1619
case ButtonPosition.NE:
17-
return styles.buttonPositionNE;
20+
return responsiveStyles.buttonPositionNE;
1821
case ButtonPosition.NW:
19-
return styles.buttonPositionNW;
22+
return responsiveStyles.buttonPositionNW;
2023
case ButtonPosition.N:
21-
return styles.buttonPositionN;
24+
return responsiveStyles.buttonPositionN;
2225
default:
2326
return {};
2427
}
@@ -32,19 +35,19 @@ export const TopControls: React.FC<TopControlsProps> = ({
3235
// If we have top-positioned buttons, render them with absolute positioning
3336
if (hasTopPositionedButtons) {
3437
return (
35-
<View style={styles.topControlsBar}>
38+
<View style={responsiveStyles.topControlsBar}>
3639
{/* Invisible spacer to maintain layout */}
3740
{onBack && backButtonPosition && backButtonPosition !== ButtonPosition.SE && (
3841
<TouchableOpacity
39-
style={[styles.topButton, getPositionStyle(backButtonPosition)]}
42+
style={[responsiveStyles.topButton, getPositionStyle(backButtonPosition)]}
4043
onPress={onBack}
4144
>
4245
<Ionicons name="close" size={ICON_SIZES.top} color="white" />
4346
</TouchableOpacity>
4447
)}
4548
{shareButtonPosition && shareButtonPosition !== ButtonPosition.SE && (
4649
<TouchableOpacity
47-
style={[styles.topButton, getPositionStyle(shareButtonPosition)]}
50+
style={[responsiveStyles.topButton, getPositionStyle(shareButtonPosition)]}
4851
onPress={onShare}
4952
>
5053
<Ionicons name="share-outline" size={ICON_SIZES.top} color="white" />
@@ -57,19 +60,19 @@ export const TopControls: React.FC<TopControlsProps> = ({
5760
// Default layout (original behavior) - only if no positioning is specified
5861
if (!backButtonPosition && !shareButtonPosition) {
5962
return (
60-
<View style={styles.topControlsBar}>
63+
<View style={responsiveStyles.topControlsBar}>
6164
{onBack && (
62-
<TouchableOpacity style={styles.topButton} onPress={onBack}>
65+
<TouchableOpacity style={responsiveStyles.topButton} onPress={onBack}>
6366
<Ionicons name="close" size={ICON_SIZES.top} color="white" />
6467
</TouchableOpacity>
6568
)}
66-
<TouchableOpacity style={styles.topButton} onPress={onShare}>
69+
<TouchableOpacity style={responsiveStyles.topButton} onPress={onShare}>
6770
<Ionicons name="share-outline" size={ICON_SIZES.top} color="white" />
6871
</TouchableOpacity>
6972
</View>
7073
);
7174
}
7275

7376
// Return empty spacer if only SE buttons are specified
74-
return <View style={styles.topControlsBar} />;
77+
return <View style={responsiveStyles.topControlsBar} />;
7578
};

0 commit comments

Comments
 (0)