mirror of
https://github.com/movie-web/native-app.git
synced 2025-09-13 16:03:26 +00:00
Merge branch 'feat-providers-video' of https://github.com/castdrian/mw-native into pr/9
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { useRef } from "react";
|
||||||
import { Platform, View } from "react-native";
|
import { Platform, View } from "react-native";
|
||||||
import { Tabs } from "expo-router";
|
import { Tabs } from "expo-router";
|
||||||
|
|
||||||
@@ -6,87 +7,102 @@ import Colors from "@movie-web/tailwind-config/colors";
|
|||||||
import { MovieWebSvg } from "~/components/Icon";
|
import { MovieWebSvg } from "~/components/Icon";
|
||||||
import SvgTabBarIcon from "~/components/SvgTabBarIcon";
|
import SvgTabBarIcon from "~/components/SvgTabBarIcon";
|
||||||
import TabBarIcon from "~/components/TabBarIcon";
|
import TabBarIcon from "~/components/TabBarIcon";
|
||||||
|
import SearchTabContext from "./search/SearchTabContext";
|
||||||
|
|
||||||
export default function TabLayout() {
|
export default function TabLayout() {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
const focusSearchInputRef = useRef(() => {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<SearchTabContext.Provider value={{ focusSearchInputRef }}>
|
||||||
sceneContainerStyle={{
|
<Tabs
|
||||||
backgroundColor: Colors.background,
|
sceneContainerStyle={{
|
||||||
}}
|
backgroundColor: Colors.background,
|
||||||
screenOptions={{
|
}}
|
||||||
headerShown: false,
|
screenListeners={({ route }) => ({
|
||||||
tabBarActiveTintColor: Colors.primary[100],
|
tabPress: () => {
|
||||||
tabBarStyle: {
|
switch (route.name) {
|
||||||
backgroundColor: Colors.secondary[700],
|
case "search":
|
||||||
borderTopColor: "transparent",
|
focusSearchInputRef.current();
|
||||||
borderTopRightRadius: 20,
|
break;
|
||||||
borderTopLeftRadius: 20,
|
}
|
||||||
paddingBottom: Platform.select({ ios: 100 }),
|
|
||||||
height: 80,
|
|
||||||
},
|
|
||||||
tabBarItemStyle: {
|
|
||||||
paddingVertical: 18,
|
|
||||||
height: 82,
|
|
||||||
},
|
|
||||||
tabBarLabelStyle: [
|
|
||||||
{
|
|
||||||
marginTop: 2,
|
|
||||||
},
|
},
|
||||||
],
|
})}
|
||||||
}}
|
screenOptions={{
|
||||||
>
|
headerShown: false,
|
||||||
<Tabs.Screen
|
tabBarActiveTintColor: Colors.primary[100],
|
||||||
name="index"
|
tabBarStyle: {
|
||||||
options={{
|
backgroundColor: Colors.secondary[700],
|
||||||
title: "Home",
|
borderTopColor: "transparent",
|
||||||
tabBarIcon: ({ focused }) => (
|
borderTopRightRadius: 20,
|
||||||
<TabBarIcon name="home" focused={focused} />
|
borderTopLeftRadius: 20,
|
||||||
),
|
paddingBottom: Platform.select({ ios: 100 }),
|
||||||
|
height: 80,
|
||||||
|
},
|
||||||
|
tabBarItemStyle: {
|
||||||
|
paddingVertical: 18,
|
||||||
|
height: 82,
|
||||||
|
},
|
||||||
|
tabBarLabelStyle: [
|
||||||
|
{
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="downloads"
|
name="index"
|
||||||
options={{
|
options={{
|
||||||
title: "Downloads",
|
title: "Home",
|
||||||
tabBarIcon: ({ focused }) => (
|
tabBarIcon: ({ focused }) => (
|
||||||
<TabBarIcon name="download" focused={focused} />
|
<TabBarIcon name="home" focused={focused} />
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="search"
|
name="downloads"
|
||||||
options={{
|
options={{
|
||||||
title: "Search",
|
title: "Downloads",
|
||||||
tabBarLabel: "",
|
tabBarIcon: ({ focused }) => (
|
||||||
tabBarIcon: ({ focused }) => (
|
<TabBarIcon name="download" focused={focused} />
|
||||||
<View
|
),
|
||||||
className={`android:top-2 ios:top-2 flex h-14 w-14 items-center justify-center overflow-hidden rounded-full ${focused ? "bg-primary-300" : "bg-primary-400"} text-center align-middle text-2xl text-white`}
|
}}
|
||||||
>
|
/>
|
||||||
<TabBarIcon name="search" color="#FFF" />
|
<Tabs.Screen
|
||||||
</View>
|
name="search"
|
||||||
),
|
options={{
|
||||||
}}
|
title: "Search",
|
||||||
/>
|
tabBarLabel: "",
|
||||||
<Tabs.Screen
|
tabBarIcon: ({ focused }) => (
|
||||||
name="movie-web"
|
<View
|
||||||
options={{
|
className={`android:top-2 ios:top-2 flex h-14 w-14 items-center justify-center overflow-hidden rounded-full ${focused ? "bg-primary-300" : "bg-primary-400"} text-center align-middle text-2xl text-white`}
|
||||||
title: "movie-web",
|
>
|
||||||
tabBarIcon: ({ focused }) => (
|
<TabBarIcon name="search" color="#FFF" />
|
||||||
<SvgTabBarIcon focused={focused}>
|
</View>
|
||||||
<MovieWebSvg />
|
),
|
||||||
</SvgTabBarIcon>
|
}}
|
||||||
),
|
/>
|
||||||
}}
|
<Tabs.Screen
|
||||||
/>
|
name="movie-web"
|
||||||
<Tabs.Screen
|
options={{
|
||||||
name="settings"
|
title: "movie-web",
|
||||||
options={{
|
tabBarIcon: ({ focused }) => (
|
||||||
title: "Settings",
|
<SvgTabBarIcon focused={focused}>
|
||||||
tabBarIcon: ({ focused }) => (
|
<MovieWebSvg />
|
||||||
<TabBarIcon name="cog" focused={focused} />
|
</SvgTabBarIcon>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tabs>
|
<Tabs.Screen
|
||||||
|
name="settings"
|
||||||
|
options={{
|
||||||
|
title: "Settings",
|
||||||
|
tabBarIcon: ({ focused }) => (
|
||||||
|
<TabBarIcon name="cog" focused={focused} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
</SearchTabContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
8
apps/expo/src/app/(tabs)/search/SearchTabContext.tsx
Normal file
8
apps/expo/src/app/(tabs)/search/SearchTabContext.tsx
Normal file
@@ -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;
|
@@ -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 { TextInput, View } from "react-native";
|
||||||
import { useFocusEffect } from "expo-router";
|
import { useFocusEffect } from "expo-router";
|
||||||
import { FontAwesome5 } from "@expo/vector-icons";
|
import { FontAwesome5 } from "@expo/vector-icons";
|
||||||
|
|
||||||
import Colors from "@movie-web/tailwind-config/colors";
|
import Colors from "@movie-web/tailwind-config/colors";
|
||||||
|
|
||||||
|
import SearchTabContext from "./SearchTabContext";
|
||||||
|
|
||||||
export default function Searchbar({
|
export default function Searchbar({
|
||||||
onSearchChange,
|
onSearchChange,
|
||||||
}: {
|
}: {
|
||||||
@@ -13,6 +15,14 @@ export default function Searchbar({
|
|||||||
const [keyword, setKeyword] = useState("");
|
const [keyword, setKeyword] = useState("");
|
||||||
const inputRef = useRef<TextInput>(null);
|
const inputRef = useRef<TextInput>(null);
|
||||||
|
|
||||||
|
const { focusSearchInputRef } = useContext(SearchTabContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
focusSearchInputRef.current = () => {
|
||||||
|
inputRef.current?.focus();
|
||||||
|
};
|
||||||
|
}, [focusSearchInputRef]);
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
// When the screen is focused
|
// When the screen is focused
|
||||||
|
@@ -80,7 +80,7 @@ export default function SearchScreen() {
|
|||||||
<ScrollView
|
<ScrollView
|
||||||
onScrollBeginDrag={handleScrollBegin}
|
onScrollBeginDrag={handleScrollBegin}
|
||||||
onMomentumScrollEnd={handleScrollEnd}
|
onMomentumScrollEnd={handleScrollEnd}
|
||||||
scrollEnabled={data && data.length > 0}
|
scrollEnabled={data && data.length > 0 ? true : false}
|
||||||
keyboardDismissMode="on-drag"
|
keyboardDismissMode="on-drag"
|
||||||
keyboardShouldPersistTaps="handled"
|
keyboardShouldPersistTaps="handled"
|
||||||
>
|
>
|
||||||
|
@@ -47,6 +47,7 @@ export const VideoPlayer = () => {
|
|||||||
const [hasStartedPlaying, setHasStartedPlaying] = useState(false);
|
const [hasStartedPlaying, setHasStartedPlaying] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const scale = useSharedValue(1);
|
const scale = useSharedValue(1);
|
||||||
|
const [lastVelocityY, setLastVelocityY] = useState(0);
|
||||||
|
|
||||||
const isIdle = usePlayerStore((state) => state.interface.isIdle);
|
const isIdle = usePlayerStore((state) => state.interface.isIdle);
|
||||||
const stream = usePlayerStore((state) => state.interface.currentStream);
|
const stream = usePlayerStore((state) => state.interface.currentStream);
|
||||||
@@ -87,26 +88,34 @@ export const VideoPlayer = () => {
|
|||||||
|
|
||||||
const directionMultiplier = event.velocityY < 0 ? 1 : -1;
|
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) {
|
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);
|
runOnJS(handleVolumeChange)(newVolume);
|
||||||
} else {
|
} else {
|
||||||
const change =
|
|
||||||
directionMultiplier * Math.abs(event.velocityY / divisor);
|
|
||||||
const newBrightness = Math.max(
|
|
||||||
0,
|
|
||||||
Math.min(1, brightness.value + change),
|
|
||||||
);
|
|
||||||
brightness.value = newBrightness;
|
brightness.value = newBrightness;
|
||||||
runOnJS(handleBrightnessChange)(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(() => {
|
.onEnd(() => {
|
||||||
|
runOnJS(setLastVelocityY)(0);
|
||||||
runOnJS(setShowVolumeOverlay)(false);
|
runOnJS(setShowVolumeOverlay)(false);
|
||||||
runOnJS(setShowBrightnessOverlay)(false);
|
runOnJS(setShowBrightnessOverlay)(false);
|
||||||
});
|
});
|
||||||
|
@@ -24,17 +24,20 @@ export const useBrightness = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
}, []);
|
}, [brightness]);
|
||||||
|
|
||||||
const handleBrightnessChange = useCallback(async (newValue: number) => {
|
const handleBrightnessChange = useCallback(
|
||||||
try {
|
async (newValue: number) => {
|
||||||
setShowBrightnessOverlay(true);
|
try {
|
||||||
brightness.value = newValue;
|
setShowBrightnessOverlay(true);
|
||||||
await Brightness.setBrightnessAsync(newValue);
|
brightness.value = newValue;
|
||||||
} catch (error) {
|
await Brightness.setBrightnessAsync(newValue);
|
||||||
console.error("Failed to set brightness:", error);
|
} catch (error) {
|
||||||
}
|
console.error("Failed to set brightness:", error);
|
||||||
}, []);
|
}
|
||||||
|
},
|
||||||
|
[brightness],
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
showBrightnessOverlay: debouncedShowBrightnessOverlay,
|
showBrightnessOverlay: debouncedShowBrightnessOverlay,
|
||||||
|
@@ -9,10 +9,13 @@ export const useVolume = () => {
|
|||||||
const volume = useSharedValue(1);
|
const volume = useSharedValue(1);
|
||||||
const debouncedVolume = useDebounce(volume.value, 20);
|
const debouncedVolume = useDebounce(volume.value, 20);
|
||||||
|
|
||||||
const handleVolumeChange = useCallback((newValue: number) => {
|
const handleVolumeChange = useCallback(
|
||||||
volume.value = newValue;
|
(newValue: number) => {
|
||||||
setShowVolumeOverlay(true);
|
volume.value = newValue;
|
||||||
}, []);
|
setShowVolumeOverlay(true);
|
||||||
|
},
|
||||||
|
[volume],
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
showVolumeOverlay: debouncedShowVolumeOverlay,
|
showVolumeOverlay: debouncedShowVolumeOverlay,
|
||||||
|
Reference in New Issue
Block a user