diff --git a/apps/expo/src/app/(tabs)/_layout.tsx b/apps/expo/src/app/(tabs)/_layout.tsx index e927659..84c88b0 100644 --- a/apps/expo/src/app/(tabs)/_layout.tsx +++ b/apps/expo/src/app/(tabs)/_layout.tsx @@ -1,3 +1,4 @@ +import { useRef } from "react"; import { Platform, View } from "react-native"; import { Tabs } from "expo-router"; @@ -6,87 +7,102 @@ import Colors from "@movie-web/tailwind-config/colors"; import { MovieWebSvg } from "~/components/Icon"; import SvgTabBarIcon from "~/components/SvgTabBarIcon"; import TabBarIcon from "~/components/TabBarIcon"; +import SearchTabContext from "./search/SearchTabContext"; export default function TabLayout() { + // eslint-disable-next-line @typescript-eslint/no-empty-function + const focusSearchInputRef = useRef(() => {}); + return ( - + ({ + tabPress: () => { + switch (route.name) { + case "search": + focusSearchInputRef.current(); + break; + } }, - ], - }} - > - ( - - ), + })} + screenOptions={{ + headerShown: false, + tabBarActiveTintColor: Colors.primary[100], + tabBarStyle: { + backgroundColor: Colors.secondary[700], + borderTopColor: "transparent", + borderTopRightRadius: 20, + borderTopLeftRadius: 20, + paddingBottom: Platform.select({ ios: 100 }), + height: 80, + }, + tabBarItemStyle: { + paddingVertical: 18, + height: 82, + }, + tabBarLabelStyle: [ + { + marginTop: 2, + }, + ], }} - /> - ( - - ), - }} - /> - ( - - - - ), - }} - /> - ( - - - - ), - }} - /> - ( - - ), - }} - /> - + > + ( + + ), + }} + /> + ( + + ), + }} + /> + ( + + + + ), + }} + /> + ( + + + + ), + }} + /> + ( + + ), + }} + /> + + ); } diff --git a/apps/expo/src/app/(tabs)/search/SearchTabContext.tsx b/apps/expo/src/app/(tabs)/search/SearchTabContext.tsx new file mode 100644 index 0000000..ab67870 --- /dev/null +++ b/apps/expo/src/app/(tabs)/search/SearchTabContext.tsx @@ -0,0 +1,8 @@ +import React from "react"; + +const SearchTabContext = React.createContext({ + // eslint-disable-next-line @typescript-eslint/no-empty-function + focusSearchInputRef: { current: () => {} }, +}); + +export default SearchTabContext; diff --git a/apps/expo/src/app/(tabs)/search/Searchbar.tsx b/apps/expo/src/app/(tabs)/search/Searchbar.tsx index 0125e92..a662ec8 100644 --- a/apps/expo/src/app/(tabs)/search/Searchbar.tsx +++ b/apps/expo/src/app/(tabs)/search/Searchbar.tsx @@ -1,10 +1,12 @@ -import { useCallback, useRef, useState } from "react"; +import { useCallback, useContext, useEffect, useRef, useState } from "react"; import { TextInput, View } from "react-native"; import { useFocusEffect } from "expo-router"; import { FontAwesome5 } from "@expo/vector-icons"; import Colors from "@movie-web/tailwind-config/colors"; +import SearchTabContext from "./SearchTabContext"; + export default function Searchbar({ onSearchChange, }: { @@ -13,6 +15,14 @@ export default function Searchbar({ const [keyword, setKeyword] = useState(""); const inputRef = useRef(null); + const { focusSearchInputRef } = useContext(SearchTabContext); + + useEffect(() => { + focusSearchInputRef.current = () => { + inputRef.current?.focus(); + }; + }, [focusSearchInputRef]); + useFocusEffect( useCallback(() => { // When the screen is focused diff --git a/apps/expo/src/app/(tabs)/search/_layout.tsx b/apps/expo/src/app/(tabs)/search/_layout.tsx index a0d07ca..2e2465a 100644 --- a/apps/expo/src/app/(tabs)/search/_layout.tsx +++ b/apps/expo/src/app/(tabs)/search/_layout.tsx @@ -80,7 +80,7 @@ export default function SearchScreen() { 0} + scrollEnabled={data && data.length > 0 ? true : false} keyboardDismissMode="on-drag" keyboardShouldPersistTaps="handled" > diff --git a/apps/expo/src/components/player/VideoPlayer.tsx b/apps/expo/src/components/player/VideoPlayer.tsx index c3f5277..2e71865 100644 --- a/apps/expo/src/components/player/VideoPlayer.tsx +++ b/apps/expo/src/components/player/VideoPlayer.tsx @@ -47,6 +47,7 @@ export const VideoPlayer = () => { const [hasStartedPlaying, setHasStartedPlaying] = useState(false); const router = useRouter(); const scale = useSharedValue(1); + const [lastVelocityY, setLastVelocityY] = useState(0); const isIdle = usePlayerStore((state) => state.interface.isIdle); const stream = usePlayerStore((state) => state.interface.currentStream); @@ -87,26 +88,34 @@ export const VideoPlayer = () => { const directionMultiplier = event.velocityY < 0 ? 1 : -1; + const change = directionMultiplier * Math.abs(event.velocityY / divisor); + const newVolume = Math.max(0, Math.min(1, currentVolume.value + change)); + const newBrightness = Math.max(0, Math.min(1, brightness.value + change)); + if (event.x > screenHalfWidth) { - const change = - directionMultiplier * Math.abs(event.velocityY / divisor); - const newVolume = Math.max( - 0, - Math.min(1, currentVolume.value + change), - ); runOnJS(handleVolumeChange)(newVolume); } else { - const change = - directionMultiplier * Math.abs(event.velocityY / divisor); - const newBrightness = Math.max( - 0, - Math.min(1, brightness.value + change), - ); brightness.value = newBrightness; runOnJS(handleBrightnessChange)(newBrightness); } + + if ( + (event.velocityY < 0 && lastVelocityY >= 0) || + (event.velocityY >= 0 && lastVelocityY < 0) + ) { + runOnJS(setLastVelocityY)(event.velocityY); + } + + if (event.x > screenHalfWidth) { + runOnJS(handleVolumeChange)(newVolume); + runOnJS(setShowVolumeOverlay)(true); + } else { + runOnJS(handleBrightnessChange)(newBrightness); + runOnJS(setShowBrightnessOverlay)(true); + } }) .onEnd(() => { + runOnJS(setLastVelocityY)(0); runOnJS(setShowVolumeOverlay)(false); runOnJS(setShowBrightnessOverlay)(false); }); diff --git a/apps/expo/src/hooks/player/useBrightness.ts b/apps/expo/src/hooks/player/useBrightness.ts index 844c4b2..4e6bfe5 100644 --- a/apps/expo/src/hooks/player/useBrightness.ts +++ b/apps/expo/src/hooks/player/useBrightness.ts @@ -24,17 +24,20 @@ export const useBrightness = () => { } void init(); - }, []); + }, [brightness]); - const handleBrightnessChange = useCallback(async (newValue: number) => { - try { - setShowBrightnessOverlay(true); - brightness.value = newValue; - await Brightness.setBrightnessAsync(newValue); - } catch (error) { - console.error("Failed to set brightness:", error); - } - }, []); + const handleBrightnessChange = useCallback( + async (newValue: number) => { + try { + setShowBrightnessOverlay(true); + brightness.value = newValue; + await Brightness.setBrightnessAsync(newValue); + } catch (error) { + console.error("Failed to set brightness:", error); + } + }, + [brightness], + ); return { showBrightnessOverlay: debouncedShowBrightnessOverlay, diff --git a/apps/expo/src/hooks/player/useVolume.ts b/apps/expo/src/hooks/player/useVolume.ts index 42e5661..4722bf5 100644 --- a/apps/expo/src/hooks/player/useVolume.ts +++ b/apps/expo/src/hooks/player/useVolume.ts @@ -9,10 +9,13 @@ export const useVolume = () => { const volume = useSharedValue(1); const debouncedVolume = useDebounce(volume.value, 20); - const handleVolumeChange = useCallback((newValue: number) => { - volume.value = newValue; - setShowVolumeOverlay(true); - }, []); + const handleVolumeChange = useCallback( + (newValue: number) => { + volume.value = newValue; + setShowVolumeOverlay(true); + }, + [volume], + ); return { showVolumeOverlay: debouncedShowVolumeOverlay,