diff --git a/.github/workflows/build-apps.yml b/.github/workflows/build-apps.yml index 3a50064c..15a7b03a 100644 --- a/.github/workflows/build-apps.yml +++ b/.github/workflows/build-apps.yml @@ -43,7 +43,7 @@ jobs: swap-storage: false - name: 📥 Checkout code - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} fetch-depth: 0 @@ -57,7 +57,7 @@ jobs: bun-version: "1.3.14" - name: 💾 Cache Bun dependencies - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0 with: path: ~/.bun/install/cache key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock') }} @@ -79,7 +79,7 @@ jobs: java-version: "17" - name: 💾 Cache Gradle global - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0 with: path: | ~/.gradle/caches/modules-2 @@ -92,7 +92,7 @@ jobs: run: bun run prebuild - name: 💾 Cache project Gradle (.gradle) - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0 with: path: android/.gradle key: ${{ runner.os }}-${{ runner.arch }}-android-gradle-develop-${{ hashFiles('android/**/build.gradle', 'android/gradle/wrapper/gradle-wrapper.properties') }} @@ -143,7 +143,7 @@ jobs: swap-storage: false - name: 📥 Checkout code - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} fetch-depth: 0 @@ -157,7 +157,7 @@ jobs: bun-version: "1.3.14" - name: 💾 Cache Bun dependencies - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0 with: path: ~/.bun/install/cache key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock') }} @@ -179,7 +179,7 @@ jobs: java-version: "17" - name: 💾 Cache Gradle global - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0 with: path: | ~/.gradle/caches/modules-2 @@ -192,7 +192,7 @@ jobs: run: bun run prebuild:tv - name: 💾 Cache project Gradle (.gradle) - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0 with: path: android/.gradle key: ${{ runner.os }}-${{ runner.arch }}-android-gradle-develop-${{ hashFiles('android/**/build.gradle', 'android/gradle/wrapper/gradle-wrapper.properties') }} @@ -230,7 +230,7 @@ jobs: steps: - name: 📥 Checkout code - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} fetch-depth: 0 @@ -244,7 +244,7 @@ jobs: bun-version: "1.3.14" - name: 💾 Cache Bun dependencies - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0 with: path: ~/.bun/install/cache key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock') }} @@ -302,7 +302,7 @@ jobs: steps: - name: 📥 Checkout code - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} fetch-depth: 0 @@ -316,7 +316,7 @@ jobs: bun-version: "1.3.14" - name: 💾 Cache Bun dependencies - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0 with: path: ~/.bun/install/cache key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock') }} @@ -369,7 +369,7 @@ jobs: steps: - name: 📥 Checkout code - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} fetch-depth: 0 @@ -383,7 +383,7 @@ jobs: bun-version: "1.3.14" - name: 💾 Cache Bun dependencies - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0 with: path: ~/.bun/install/cache key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock') }} @@ -439,7 +439,7 @@ jobs: steps: - name: 📥 Checkout code - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} fetch-depth: 0 @@ -453,7 +453,7 @@ jobs: bun-version: "1.3.14" - name: 💾 Cache Bun dependencies - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0 with: path: ~/.bun/install/cache key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock') }} diff --git a/.github/workflows/check-lockfile.yml b/.github/workflows/check-lockfile.yml index efb5f221..d4165055 100644 --- a/.github/workflows/check-lockfile.yml +++ b/.github/workflows/check-lockfile.yml @@ -19,7 +19,7 @@ jobs: steps: - name: 📥 Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} show-progress: false @@ -33,7 +33,7 @@ jobs: bun-version: "1.3.14" - name: 💾 Cache Bun dependencies - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0 with: path: | ~/.bun/install/cache diff --git a/.github/workflows/ci-codeql.yml b/.github/workflows/ci-codeql.yml index b77665f5..b9921780 100644 --- a/.github/workflows/ci-codeql.yml +++ b/.github/workflows/ci-codeql.yml @@ -27,7 +27,7 @@ jobs: steps: - name: 📥 Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: 🏁 Initialize CodeQL uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml index 39883d8c..c14fe48f 100644 --- a/.github/workflows/crowdin.yml +++ b/.github/workflows/crowdin.yml @@ -23,7 +23,7 @@ jobs: steps: - name: 📥 Checkout Repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 diff --git a/.github/workflows/detect-duplicate.yml b/.github/workflows/detect-duplicate.yml index 26da4f57..ebf515d7 100644 --- a/.github/workflows/detect-duplicate.yml +++ b/.github/workflows/detect-duplicate.yml @@ -21,7 +21,7 @@ jobs: contents: read steps: - name: 📥 Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: 🍞 Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index d36da31f..f8799f26 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -51,7 +51,7 @@ jobs: contents: read steps: - name: Checkout Repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} fetch-depth: 0 @@ -68,7 +68,7 @@ jobs: runs-on: ubuntu-26.04 steps: - name: 🛒 Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} submodules: recursive @@ -104,7 +104,7 @@ jobs: steps: - name: "📥 Checkout PR code" - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} submodules: recursive @@ -114,7 +114,7 @@ jobs: uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: # renovate: datasource=node-version depName=node versioning=node - node-version: "24.16.0" + node-version: "24.18.0" - name: "🍞 Setup Bun" uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 454f8645..027eab0d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,7 +64,7 @@ jobs: steps: - name: 📥 Checkout code - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 submodules: recursive @@ -77,7 +77,7 @@ jobs: bun-version: "1.3.14" - name: 💾 Cache Bun dependencies - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0 with: path: ~/.bun/install/cache key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock') }} @@ -184,7 +184,7 @@ jobs: actions: read # required for `gh run download` to list/fetch this run's artifacts steps: - name: 📥 Checkout code - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 show-progress: false diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index 2f02dcfc..2e0f307b 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -27,7 +27,7 @@ jobs: security-events: write # upload SARIF to code scanning steps: - name: 📥 Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 # Trivy's own action caches the vulnerability DB + binary internally # (cache-trivy-* / trivy-binary-* entries), so no manual ~/.cache/trivy diff --git a/.github/workflows/update-issue-form.yml b/.github/workflows/update-issue-form.yml index 0754735e..7f1ace97 100644 --- a/.github/workflows/update-issue-form.yml +++ b/.github/workflows/update-issue-form.yml @@ -26,7 +26,7 @@ jobs: pull-requests: write steps: - name: 📥 Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: # On `release` events GITHUB_SHA is the tagged commit — without this the # script would regenerate the form from the tag's (stale) copy and the bot diff --git a/README.md b/README.md index 258005ef..3d4221f4 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Thanks to [@Alexk2309](https://github.com/Alexk2309) for the hard work building ## 🛣️ Roadmap -Check out our [Roadmap](https://github.com/users/fredrikburmester/projects/5) To see what we're working on next, we are always open to feedback and suggestions. Please let us know if you have any ideas or feature requests. +Check out our [Roadmap](https://github.com/orgs/streamyfin/projects/3/views/1) to see what we're working on next, we are always open to feedback and suggestions. Please let us know if you have any ideas or feature requests. ## 📥 Download Streamyfin diff --git a/app/(auth)/(tabs)/(search)/index.tsx b/app/(auth)/(tabs)/(search)/index.tsx index 29461b49..759dae85 100644 --- a/app/(auth)/(tabs)/(search)/index.tsx +++ b/app/(auth)/(tabs)/(search)/index.tsx @@ -305,6 +305,8 @@ export default function SearchPage() { }, hideWhenScrolling: false, autoFocus: false, + // Android: color of the user-typed text (was dark and unreadable on the dark header) + textColor: "#fff", // Android: placeholder and icon color hintTextColor: "#fff", headerIconColor: "#fff", diff --git a/app/(auth)/(tabs)/_layout.tsx b/app/(auth)/(tabs)/_layout.tsx index 53fbeb91..45f246a5 100644 --- a/app/(auth)/(tabs)/_layout.tsx +++ b/app/(auth)/(tabs)/_layout.tsx @@ -3,16 +3,24 @@ import { type NativeBottomTabNavigationEventMap, type NativeBottomTabNavigationOptions, } from "@bottom-tabs/react-navigation"; -import { withLayoutContext } from "expo-router"; +import { Stack, useSegments, withLayoutContext } from "expo-router"; import type { ParamListBase, TabNavigationState, } from "expo-router/react-navigation"; +import { useCallback, useEffect, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { Platform, View } from "react-native"; import { SystemBars } from "react-native-edge-to-edge"; +import type { TVNavBarTab } from "@/components/tv/TVNavBar"; +import { TVNavBar } from "@/components/tv/TVNavBar"; import { Colors } from "@/constants/Colors"; -import { useTVHomeBackHandler } from "@/hooks/useTVBackHandler"; +import useRouter from "@/hooks/useAppRouter"; +import { + isTabRoute, + useTVHomeBackHandler, + useTVTabRootBackHandler, +} from "@/hooks/useTVBackHandler"; import { useSettings } from "@/utils/atoms/settings"; import { eventBus } from "@/utils/eventBus"; @@ -33,13 +41,108 @@ export const NativeTabs = withLayoutContext< NativeBottomTabNavigationEventMap >(Navigator); +const IS_ANDROID_TV = Platform.isTV && Platform.OS === "android"; + +function TVTabLayout() { + const { settings } = useSettings(); + const { t } = useTranslation(); + const segments = useSegments(); + const router = useRouter(); + + const currentTab = segments.find(isTabRoute); + const lastSegment = segments[segments.length - 1] ?? ""; + const atTabRoot = isTabRoute(lastSegment) || lastSegment === "index"; + + const tabs: TVNavBarTab[] = useMemo( + () => + [ + { key: "(home)", label: t("tabs.home") }, + { key: "(search)", label: t("tabs.search") }, + { key: "(favorites)", label: t("tabs.favorites") }, + !settings?.streamyStatsServerUrl || settings?.hideWatchlistsTab + ? null + : { key: "(watchlists)", label: t("watchlists.title") }, + { key: "(libraries)", label: t("tabs.library") }, + !settings?.showCustomMenuLinks + ? null + : { key: "(custom-links)", label: t("tabs.custom_links") }, + { key: "(settings)", label: t("tabs.settings") }, + ].filter((tab): tab is TVNavBarTab => tab !== null), + [ + settings?.streamyStatsServerUrl, + settings?.hideWatchlistsTab, + settings?.showCustomMenuLinks, + t, + ], + ); + + const activeTabKey = currentTab ?? "(home)"; + + const visibleKeys = useMemo( + () => new Set(tabs.map((tab) => tab.key)), + [tabs], + ); + + const handleTabChange = useCallback( + (key: string) => { + if (key === currentTab) return; + + if (key === "(home)") eventBus.emit("scrollToTop"); + if (key === "(search)") eventBus.emit("searchTabPressed"); + + router.replace(`/(auth)/(tabs)/${key}`); + }, + [currentTab, router], + ); + + const navigateHome = useCallback(() => { + router.replace("/(auth)/(tabs)/(home)"); + }, [router]); + useTVTabRootBackHandler(navigateHome, atTabRoot, currentTab); + + // If current tab is no longer visible (setting changed), navigate to home + useEffect(() => { + if (!visibleKeys.has(activeTabKey) && activeTabKey !== "(home)") { + router.replace("/(auth)/(tabs)/(home)"); + } + }, [visibleKeys, activeTabKey, router]); + + return ( + + + ); +} + export default function TabLayout() { const { settings } = useSettings(); const { t } = useTranslation(); - // Handle TV back button - prevent app exit when at root + // Must be called before any conditional return (rules of hooks) useTVHomeBackHandler(); + if (IS_ANDROID_TV) { + return ; + } + return (