Merge branch 'feat/native-tabbar' into pr/178

This commit is contained in:
Fredrik Burmester
2024-11-10 09:31:53 +01:00
16 changed files with 169 additions and 150 deletions

2
.gitignore vendored
View File

@@ -26,6 +26,8 @@ package-lock.json
/ios
/android
modules/vlc-player/android
pc-api-7079014811501811218-719-3b9f15aeccf8.json
credentials.json
*.apk

View File

@@ -100,7 +100,12 @@
"motionPermission": "Allow Streamyfin to access your device motion for landscape video watching."
}
],
"expo-asset"
"expo-asset",
[
"react-native-edge-to-edge",
{ "android": { "parentTheme": "Material3" } }
],
["react-native-bottom-tabs"]
],
"experiments": {
"typedRoutes": true

View File

@@ -5,7 +5,6 @@ import { ScrollingCollectionList } from "@/components/home/ScrollingCollectionLi
import { Loader } from "@/components/Loader";
import { MediaListSection } from "@/components/medialists/MediaListSection";
import { Colors } from "@/constants/Colors";
import { TAB_HEIGHT } from "@/constants/Values";
import { useDownload } from "@/providers/DownloadProvider";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { useSettings } from "@/utils/atoms/settings";
@@ -393,9 +392,6 @@ export default function index() {
paddingRight: insets.right,
paddingBottom: 16,
}}
style={{
marginBottom: TAB_HEIGHT,
}}
>
<View className="flex flex-col space-y-4">
<LargeMovieCarousel />

View File

@@ -1,4 +1,3 @@
import { HorizontalScroll } from "@/components/common/HorrizontalScroll";
import { Input } from "@/components/common/Input";
import { Text } from "@/components/common/Text";
import { TouchableItemRouter } from "@/components/common/TouchableItemRouter";
@@ -8,7 +7,6 @@ import { Loader } from "@/components/Loader";
import AlbumCover from "@/components/posters/AlbumCover";
import MoviePoster from "@/components/posters/MoviePoster";
import SeriesPoster from "@/components/posters/SeriesPoster";
import { TAB_HEIGHT } from "@/constants/Values";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { useSettings } from "@/utils/atoms/settings";
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
@@ -226,10 +224,6 @@ export default function search() {
contentContainerStyle={{
paddingLeft: insets.left,
paddingRight: insets.right,
paddingBottom: 16,
}}
style={{
marginBottom: TAB_HEIGHT,
}}
>
<View className="flex flex-col pt-2">

View File

@@ -1,87 +1,77 @@
import { TabBarIcon } from "@/components/navigation/TabBarIcon";
import React from "react";
import { Platform } from "react-native";
import { withLayoutContext } from "expo-router";
import {
createNativeBottomTabNavigator,
NativeBottomTabNavigationEventMap,
} from "react-native-bottom-tabs/react-navigation";
const { Navigator } = createNativeBottomTabNavigator();
import { BottomTabNavigationOptions } from "@react-navigation/bottom-tabs";
import { Colors } from "@/constants/Colors";
import { BlurView } from "expo-blur";
import * as NavigationBar from "expo-navigation-bar";
import { Tabs } from "expo-router";
import React, { useEffect } from "react";
import { Platform, StyleSheet } from "react-native";
import type {
ParamListBase,
TabNavigationState,
} from "@react-navigation/native";
import { SystemBars } from "react-native-edge-to-edge";
export const NativeTabs = withLayoutContext<
BottomTabNavigationOptions,
typeof Navigator,
TabNavigationState<ParamListBase>,
NativeBottomTabNavigationEventMap
>(Navigator);
export default function TabLayout() {
useEffect(() => {
if (Platform.OS === "android") {
NavigationBar.setBackgroundColorAsync("#121212");
NavigationBar.setBorderColorAsync("#121212");
}
}, []);
return (
<Tabs
initialRouteName="home"
screenOptions={{
tabBarActiveTintColor: Colors.tabIconSelected,
headerShown: false,
tabBarStyle: {
position: "absolute",
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
borderTopWidth: 0,
paddingTop: 8,
paddingBottom: Platform.OS === "android" ? 8 : 26,
height: Platform.OS === "android" ? 58 : 74,
},
tabBarBackground: () =>
Platform.OS === "ios" ? (
<BlurView
experimentalBlurMethod="dimezisBlurView"
intensity={95}
style={{
...StyleSheet.absoluteFillObject,
overflow: "hidden",
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
backgroundColor: "black",
}}
/>
) : undefined,
}}
>
<Tabs.Screen redirect name="index" />
<Tabs.Screen
name="(home)"
options={{
headerShown: false,
title: "Home",
tabBarIcon: ({ color, focused }) => (
<TabBarIcon
name={focused ? "home" : "home-outline"}
color={color}
/>
),
}}
/>
<Tabs.Screen
name="(search)"
options={{
headerShown: false,
title: "Search",
tabBarIcon: ({ color, focused }) => (
<TabBarIcon name={focused ? "search" : "search"} color={color} />
),
}}
/>
<Tabs.Screen
name="(libraries)"
options={{
headerShown: false,
title: "Library",
tabBarIcon: ({ color, focused }) => (
<TabBarIcon
name={focused ? "apps" : "apps-outline"}
color={color}
/>
),
}}
/>
</Tabs>
<>
<SystemBars hidden={false} style="light" />
<NativeTabs
sidebarAdaptable
ignoresTopSafeArea
barTintColor={Platform.OS === "android" ? "#121212" : undefined}
tabBarActiveTintColor={Colors.primary}
scrollEdgeAppearance="default"
>
<NativeTabs.Screen redirect name="index" />
<NativeTabs.Screen
name="(home)"
options={{
title: "Home",
tabBarIcon:
Platform.OS == "android"
? ({ color, focused, size }) =>
require("@/assets/icons/house.fill.png")
: () => ({ sfSymbol: "house" }),
}}
/>
<NativeTabs.Screen
name="(search)"
options={{
title: "Search",
tabBarIcon:
Platform.OS == "android"
? ({ color, focused, size }) =>
require("@/assets/icons/magnifyingglass.png")
: () => ({ sfSymbol: "magnifyingglass" }),
}}
/>
<NativeTabs.Screen
name="(libraries)"
options={{
title: "Library",
tabBarIcon:
Platform.OS == "android"
? ({ color, focused, size }) =>
require("@/assets/icons/server.rack.png")
: () => ({ sfSymbol: "rectangle.stack" }),
}}
/>
</NativeTabs>
</>
);
}

View File

@@ -1,5 +1,4 @@
import { Controls } from "@/components/video-player/Controls";
import { useAndroidNavigationBar } from "@/hooks/useAndroidNavigationBar";
import { useOrientation } from "@/hooks/useOrientation";
import { useOrientationSettings } from "@/hooks/useOrientationSettings";
import { apiAtom } from "@/providers/JellyfinProvider";
@@ -13,7 +12,8 @@ import * as Haptics from "expo-haptics";
import { useFocusEffect } from "expo-router";
import { useAtomValue } from "jotai";
import React, { useCallback, useMemo, useRef, useState } from "react";
import { Pressable, StatusBar, useWindowDimensions, View } from "react-native";
import { Pressable, useWindowDimensions, View } from "react-native";
import { SystemBars } from "react-native-edge-to-edge";
import { useSharedValue } from "react-native-reanimated";
import Video, { OnProgressData, VideoRef } from "react-native-video";
@@ -28,7 +28,6 @@ export default function page() {
const dimensions = useWindowDimensions();
useOrientation();
useOrientationSettings();
useAndroidNavigationBar();
const [showControls, setShowControls] = useState(true);
const [ignoreSafeAreas, setIgnoreSafeAreas] = useState(false);
@@ -87,7 +86,7 @@ export default function page() {
}}
className="flex flex-col items-center justify-center"
>
<StatusBar hidden />
<SystemBars hidden />
<Pressable
onPress={() => {
setShowControls(!showControls);
@@ -119,7 +118,6 @@ export default function page() {
}}
/>
</Pressable>
<Controls
item={playSettings.item}
videoRef={videoRef}

View File

@@ -31,7 +31,6 @@ import * as Notifications from "expo-notifications";
import { router, Stack } from "expo-router";
import * as ScreenOrientation from "expo-screen-orientation";
import * as SplashScreen from "expo-splash-screen";
import { StatusBar } from "expo-status-bar";
import * as TaskManager from "expo-task-manager";
import { Provider as JotaiProvider, useAtom } from "jotai";
import { useEffect, useRef } from "react";
@@ -39,6 +38,7 @@ import { AppState } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import "react-native-reanimated";
import { Toaster } from "sonner-native";
import { SystemBars } from "react-native-edge-to-edge";
SplashScreen.preventAutoHideAsync();
@@ -312,7 +312,7 @@ function Layout() {
<PlaySettingsProvider>
<DownloadProvider>
<BottomSheetModalProvider>
<StatusBar style="light" backgroundColor="#000" />
<SystemBars style="light" hidden={false} />
<ThemeProvider value={DarkTheme}>
<Stack initialRouteName="/home">
<Stack.Screen
@@ -320,6 +320,7 @@ function Layout() {
options={{
headerShown: false,
title: "",
header: () => null,
}}
/>
<Stack.Screen

BIN
assets/icons/house.fill.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -62,7 +62,7 @@ export const DownloadItem: React.FC<DownloadProps> = ({ item, ...props }) => {
getDefaultPlaySettings(item, settings);
// 4. Set states
setSelectedMediaSource(mediaSource);
setSelectedMediaSource(mediaSource ?? undefined);
setSelectedAudioStream(audioIndex ?? 0);
setSelectedSubtitleStream(subtitleIndex ?? -1);
setMaxBitrate(bitrate);

15
edge-to-edge-fix.patch Normal file
View File

@@ -0,0 +1,15 @@
--- expo.js.original 2024-11-10 09:08:19
+++ node_modules/react-native-edge-to-edge/dist/commonjs/expo.js 2024-11-10 09:08:23
@@ -19,10 +19,8 @@
const {
barStyle
} = androidStatusBar;
+ const android = props?.android || {};
const {
- android = {}
- } = props;
- const {
parentTheme = "Default"
} = android;
config.modResults.resources.style = config.modResults.resources.style?.map(style => {
\ No newline at end of file

56
expo.js.original Normal file
View File

@@ -0,0 +1,56 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _configPlugins = require("@expo/config-plugins");
const withAndroidEdgeToEdgeTheme = (config, props) => {
const themes = {
Material2: "Theme.EdgeToEdge.Material2",
Material3: "Theme.EdgeToEdge.Material3"
};
const ignoreList = new Set(["android:enforceNavigationBarContrast", "android:enforceStatusBarContrast", "android:fitsSystemWindows", "android:navigationBarColor", "android:statusBarColor", "android:windowDrawsSystemBarBackgrounds", "android:windowLayoutInDisplayCutoutMode", "android:windowLightNavigationBar", "android:windowLightStatusBar", "android:windowTranslucentNavigation", "android:windowTranslucentStatus"]);
return (0, _configPlugins.withAndroidStyles)(config, config => {
const {
androidStatusBar = {},
userInterfaceStyle = "light"
} = config;
const {
barStyle
} = androidStatusBar;
const {
android = {}
} = props;
const {
parentTheme = "Default"
} = android;
config.modResults.resources.style = config.modResults.resources.style?.map(style => {
if (style.$.name === "AppTheme") {
style.$.parent = themes[parentTheme] ?? "Theme.EdgeToEdge";
if (style.item != null) {
style.item = style.item.filter(item => !ignoreList.has(item.$.name));
}
if (barStyle != null) {
style.item.push({
$: {
name: "android:windowLightStatusBar"
},
_: String(barStyle === "dark-content")
});
} else if (userInterfaceStyle !== "automatic") {
style.item.push({
$: {
name: "android:windowLightStatusBar"
},
_: String(userInterfaceStyle === "light")
});
}
}
return style;
});
return config;
});
};
var _default = exports.default = (0, _configPlugins.createRunOncePlugin)(withAndroidEdgeToEdgeTheme, "react-native-edge-to-edge");
//# sourceMappingURL=expo.js.map

View File

@@ -1,17 +0,0 @@
import * as NavigationBar from "expo-navigation-bar";
import { useEffect } from "react";
import { Platform } from "react-native";
export const useAndroidNavigationBar = () => {
useEffect(() => {
if (Platform.OS === "android") {
NavigationBar.setVisibilityAsync("hidden");
NavigationBar.setBehaviorAsync("overlay-swipe");
return () => {
NavigationBar.setVisibilityAsync("visible");
NavigationBar.setBehaviorAsync("inset-swipe");
};
}
}, []);
};

View File

@@ -1,27 +0,0 @@
// hooks/useNavigationBarVisibility.ts
import { useEffect } from "react";
import { Platform } from "react-native";
import * as NavigationBar from "expo-navigation-bar";
export const useNavigationBarVisibility = (isPlaying: boolean | null) => {
useEffect(() => {
const handleVisibility = async () => {
if (Platform.OS === "android") {
if (isPlaying) {
await NavigationBar.setVisibilityAsync("hidden");
} else {
await NavigationBar.setVisibilityAsync("visible");
}
}
};
handleVisibility();
return () => {
if (Platform.OS === "android") {
NavigationBar.setVisibilityAsync("visible");
}
};
}, [isPlaying]);
};

View File

@@ -9,7 +9,8 @@
"ios": "expo run:ios",
"web": "expo start --web",
"test": "jest --watchAll",
"lint": "expo lint"
"lint": "expo lint",
"postinstall": "patch-package"
},
"jest": {
"preset": "jest-expo"
@@ -30,15 +31,17 @@
"@shopify/flash-list": "1.6.4",
"@tanstack/react-query": "^5.56.2",
"@types/lodash": "^4.17.9",
"@types/react-native-vector-icons": "^6.4.18",
"@types/uuid": "^10.0.0",
"add": "^2.0.6",
"axios": "^1.7.7",
"expo": "~51.0.38",
"expo-asset": "~10.0.10",
"expo": "~51.0.39",
"expo-background-fetch": "~12.0.1",
"expo-blur": "~13.0.2",
"expo-build-properties": "~0.12.5",
"expo-constants": "~16.0.2",
"expo-dev-client": "~4.0.28",
"expo-dev-client": "~4.0.29",
"expo-device": "~6.0.2",
"expo-font": "~12.0.10",
"expo-haptics": "~13.0.1",
@@ -46,13 +49,12 @@
"expo-keep-awake": "~13.0.2",
"expo-linear-gradient": "~13.0.2",
"expo-linking": "~6.3.1",
"expo-navigation-bar": "~3.0.7",
"expo-network": "~6.0.1",
"expo-notifications": "~0.28.19",
"expo-router": "~3.5.23",
"expo-router": "~3.5.24",
"expo-screen-orientation": "~7.0.5",
"expo-sensors": "~13.0.9",
"expo-splash-screen": "~0.27.6",
"expo-splash-screen": "~0.27.7",
"expo-status-bar": "~1.12.1",
"expo-system-ui": "~3.0.7",
"expo-task-manager": "~11.8.2",
@@ -67,8 +69,10 @@
"react-dom": "18.2.0",
"react-native": "0.74.5",
"react-native-awesome-slider": "^2.5.3",
"react-native-bottom-tabs": "^0.4.0",
"react-native-circular-progress": "^1.4.0",
"react-native-compressor": "^1.8.25",
"react-native-edge-to-edge": "^1.1.0",
"react-native-gesture-handler": "~2.16.1",
"react-native-get-random-values": "^1.11.0",
"react-native-google-cast": "^4.8.3",
@@ -102,6 +106,8 @@
"@types/react-test-renderer": "^18.0.7",
"jest": "^29.2.1",
"jest-expo": "~51.0.4",
"patch-package": "^8.0.0",
"postinstall-postinstall": "^2.1.0",
"react-test-renderer": "18.2.0",
"typescript": "~5.3.3"
},