mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-05-29 18:18:26 +01:00
Compare commits
3 Commits
renovate/r
...
fix/refres
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
235202383a | ||
|
|
e2e4573347 | ||
|
|
d689c63734 |
16
.github/workflows/build-apps.yml
vendored
16
.github/workflows/build-apps.yml
vendored
@@ -318,12 +318,12 @@ jobs:
|
|||||||
show-progress: false
|
show-progress: false
|
||||||
|
|
||||||
- name: 🍞 Setup Bun
|
- name: 🍞 Setup Bun
|
||||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
- name: 💾 Cache Bun dependencies
|
- name: 💾 Cache Bun dependencies
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.bun/install/cache
|
path: ~/.bun/install/cache
|
||||||
key: ${{ runner.os }}-bun-cache-${{ hashFiles('bun.lock') }}
|
key: ${{ runner.os }}-bun-cache-${{ hashFiles('bun.lock') }}
|
||||||
@@ -339,7 +339,7 @@ jobs:
|
|||||||
run: bun run prebuild:tv
|
run: bun run prebuild:tv
|
||||||
|
|
||||||
- name: 🔧 Setup Xcode
|
- name: 🔧 Setup Xcode
|
||||||
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1
|
||||||
with:
|
with:
|
||||||
xcode-version: "26.2"
|
xcode-version: "26.2"
|
||||||
|
|
||||||
@@ -359,7 +359,7 @@ jobs:
|
|||||||
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
|
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: 📤 Upload IPA artifact
|
- name: 📤 Upload IPA artifact
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: streamyfin-ios-tv-ipa-${{ env.DATE_TAG }}
|
name: streamyfin-ios-tv-ipa-${{ env.DATE_TAG }}
|
||||||
path: build-*.ipa
|
path: build-*.ipa
|
||||||
@@ -384,12 +384,12 @@ jobs:
|
|||||||
show-progress: false
|
show-progress: false
|
||||||
|
|
||||||
- name: 🍞 Setup Bun
|
- name: 🍞 Setup Bun
|
||||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||||
with:
|
with:
|
||||||
bun-version: latest
|
bun-version: latest
|
||||||
|
|
||||||
- name: 💾 Cache Bun dependencies
|
- name: 💾 Cache Bun dependencies
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||||
with:
|
with:
|
||||||
path: ~/.bun/install/cache
|
path: ~/.bun/install/cache
|
||||||
key: ${{ runner.os }}-bun-cache-${{ hashFiles('bun.lock') }}
|
key: ${{ runner.os }}-bun-cache-${{ hashFiles('bun.lock') }}
|
||||||
@@ -405,7 +405,7 @@ jobs:
|
|||||||
run: bun run prebuild:tv
|
run: bun run prebuild:tv
|
||||||
|
|
||||||
- name: 🔧 Setup Xcode
|
- name: 🔧 Setup Xcode
|
||||||
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1
|
||||||
with:
|
with:
|
||||||
xcode-version: "26.2"
|
xcode-version: "26.2"
|
||||||
|
|
||||||
@@ -418,7 +418,7 @@ jobs:
|
|||||||
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
|
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: 📤 Upload IPA artifact
|
- name: 📤 Upload IPA artifact
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: streamyfin-ios-tv-unsigned-ipa-${{ env.DATE_TAG }}
|
name: streamyfin-ios-tv-unsigned-ipa-${{ env.DATE_TAG }}
|
||||||
path: build/*.ipa
|
path: build/*.ipa
|
||||||
|
|||||||
6
.github/workflows/ci-codeql.yml
vendored
6
.github/workflows/ci-codeql.yml
vendored
@@ -27,13 +27,13 @@ jobs:
|
|||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: 🏁 Initialize CodeQL
|
- name: 🏁 Initialize CodeQL
|
||||||
uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
queries: +security-extended,security-and-quality
|
queries: +security-extended,security-and-quality
|
||||||
|
|
||||||
- name: 🛠️ Autobuild
|
- name: 🛠️ Autobuild
|
||||||
uses: github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||||
|
|
||||||
- name: 🧪 Perform CodeQL Analysis
|
- name: 🧪 Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import { MediaListSection } from "@/components/medialists/MediaListSection";
|
|||||||
import { Colors } from "@/constants/Colors";
|
import { Colors } from "@/constants/Colors";
|
||||||
import useRouter from "@/hooks/useAppRouter";
|
import useRouter from "@/hooks/useAppRouter";
|
||||||
import { useNetworkStatus } from "@/hooks/useNetworkStatus";
|
import { useNetworkStatus } from "@/hooks/useNetworkStatus";
|
||||||
|
import { useRefetchHomeOnForeground } from "@/hooks/useRefetchHomeOnForeground";
|
||||||
import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache";
|
import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache";
|
||||||
import { useDownload } from "@/providers/DownloadProvider";
|
import { useDownload } from "@/providers/DownloadProvider";
|
||||||
import { useIntroSheet } from "@/providers/IntroSheetProvider";
|
import { useIntroSheet } from "@/providers/IntroSheetProvider";
|
||||||
@@ -107,6 +108,9 @@ export const Home = () => {
|
|||||||
prevIsConnected.current = isConnected;
|
prevIsConnected.current = isConnected;
|
||||||
}, [isConnected, invalidateCache]);
|
}, [isConnected, invalidateCache]);
|
||||||
|
|
||||||
|
// Refresh home data on mount (cold start) and when app returns to foreground
|
||||||
|
useRefetchHomeOnForeground();
|
||||||
|
|
||||||
const hasDownloads = useMemo(() => {
|
const hasDownloads = useMemo(() => {
|
||||||
if (Platform.isTV) return false;
|
if (Platform.isTV) return false;
|
||||||
return downloadedItems.length > 0;
|
return downloadedItems.length > 0;
|
||||||
@@ -196,10 +200,13 @@ export const Home = () => {
|
|||||||
|
|
||||||
const refetch = async () => {
|
const refetch = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
try {
|
||||||
setLoadedSections(new Set());
|
setLoadedSections(new Set());
|
||||||
await refreshStreamyfinPluginSettings();
|
await refreshStreamyfinPluginSettings();
|
||||||
await invalidateCache();
|
await invalidateCache();
|
||||||
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createCollectionConfig = useCallback(
|
const createCollectionConfig = useCallback(
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Platform,
|
Platform,
|
||||||
|
RefreshControl,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
@@ -37,6 +38,7 @@ import { MediaListSection } from "@/components/medialists/MediaListSection";
|
|||||||
import { Colors } from "@/constants/Colors";
|
import { Colors } from "@/constants/Colors";
|
||||||
import useRouter from "@/hooks/useAppRouter";
|
import useRouter from "@/hooks/useAppRouter";
|
||||||
import { useNetworkStatus } from "@/hooks/useNetworkStatus";
|
import { useNetworkStatus } from "@/hooks/useNetworkStatus";
|
||||||
|
import { useRefetchHomeOnForeground } from "@/hooks/useRefetchHomeOnForeground";
|
||||||
import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache";
|
import { useInvalidatePlaybackProgressCache } from "@/hooks/useRevalidatePlaybackProgressCache";
|
||||||
import { useDownload } from "@/providers/DownloadProvider";
|
import { useDownload } from "@/providers/DownloadProvider";
|
||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
@@ -67,8 +69,8 @@ export const HomeWithCarousel = () => {
|
|||||||
const api = useAtomValue(apiAtom);
|
const api = useAtomValue(apiAtom);
|
||||||
const user = useAtomValue(userAtom);
|
const user = useAtomValue(userAtom);
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const [_loading, setLoading] = useState(false);
|
|
||||||
const { settings, refreshStreamyfinPluginSettings } = useSettings();
|
const { settings, refreshStreamyfinPluginSettings } = useSettings();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
const headerOverlayOffset = Platform.isTV ? 0 : 60;
|
const headerOverlayOffset = Platform.isTV ? 0 : 60;
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const animatedScrollRef = useAnimatedRef<Animated.ScrollView>();
|
const animatedScrollRef = useAnimatedRef<Animated.ScrollView>();
|
||||||
@@ -91,6 +93,19 @@ export const HomeWithCarousel = () => {
|
|||||||
prevIsConnected.current = isConnected;
|
prevIsConnected.current = isConnected;
|
||||||
}, [isConnected, invalidateCache]);
|
}, [isConnected, invalidateCache]);
|
||||||
|
|
||||||
|
// Refresh home data on mount (cold start) and when app returns to foreground
|
||||||
|
useRefetchHomeOnForeground();
|
||||||
|
|
||||||
|
const refetch = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await refreshStreamyfinPluginSettings();
|
||||||
|
await invalidateCache();
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const hasDownloads = useMemo(() => {
|
const hasDownloads = useMemo(() => {
|
||||||
if (Platform.isTV) return false;
|
if (Platform.isTV) return false;
|
||||||
return downloadedItems.length > 0;
|
return downloadedItems.length > 0;
|
||||||
@@ -178,13 +193,6 @@ export const HomeWithCarousel = () => {
|
|||||||
);
|
);
|
||||||
}, [userViews]);
|
}, [userViews]);
|
||||||
|
|
||||||
const _refetch = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
await refreshStreamyfinPluginSettings();
|
|
||||||
await invalidateCache();
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const createCollectionConfig = useCallback(
|
const createCollectionConfig = useCallback(
|
||||||
(
|
(
|
||||||
title: string,
|
title: string,
|
||||||
@@ -540,8 +548,18 @@ export const HomeWithCarousel = () => {
|
|||||||
nestedScrollEnabled
|
nestedScrollEnabled
|
||||||
contentInsetAdjustmentBehavior='never'
|
contentInsetAdjustmentBehavior='never'
|
||||||
scrollEventThrottle={16}
|
scrollEventThrottle={16}
|
||||||
bounces={false}
|
bounces={!Platform.isTV}
|
||||||
overScrollMode='never'
|
overScrollMode='never'
|
||||||
|
refreshControl={
|
||||||
|
!Platform.isTV ? (
|
||||||
|
<RefreshControl
|
||||||
|
refreshing={loading}
|
||||||
|
onRefresh={refetch}
|
||||||
|
tintColor='white'
|
||||||
|
colors={["white"]}
|
||||||
|
/>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
style={{ marginTop: -headerOverlayOffset }}
|
style={{ marginTop: -headerOverlayOffset }}
|
||||||
contentContainerStyle={{ paddingTop: headerOverlayOffset }}
|
contentContainerStyle={{ paddingTop: headerOverlayOffset }}
|
||||||
onScroll={(event) => {
|
onScroll={(event) => {
|
||||||
@@ -570,7 +588,7 @@ export const HomeWithCarousel = () => {
|
|||||||
settings.streamyStatsPromotedWatchlists;
|
settings.streamyStatsPromotedWatchlists;
|
||||||
const streamystatsSections =
|
const streamystatsSections =
|
||||||
index === streamystatsIndex && hasStreamystatsContent ? (
|
index === streamystatsIndex && hasStreamystatsContent ? (
|
||||||
<>
|
<View key='streamystats-sections'>
|
||||||
{settings.streamyStatsMovieRecommendations && (
|
{settings.streamyStatsMovieRecommendations && (
|
||||||
<StreamystatsRecommendations
|
<StreamystatsRecommendations
|
||||||
title={t(
|
title={t(
|
||||||
@@ -590,7 +608,7 @@ export const HomeWithCarousel = () => {
|
|||||||
{settings.streamyStatsPromotedWatchlists && (
|
{settings.streamyStatsPromotedWatchlists && (
|
||||||
<StreamystatsPromotedWatchlists />
|
<StreamystatsPromotedWatchlists />
|
||||||
)}
|
)}
|
||||||
</>
|
</View>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
if (section.type === "InfiniteScrollingCollectionList") {
|
if (section.type === "InfiniteScrollingCollectionList") {
|
||||||
|
|||||||
@@ -37,7 +37,18 @@ export const ItemPeopleSections: React.FC<Props> = ({ item, ...props }) => {
|
|||||||
return { ...item, People: people } as BaseItemDto;
|
return { ...item, People: people } as BaseItemDto;
|
||||||
}, [item, people]);
|
}, [item, people]);
|
||||||
|
|
||||||
const topPeople = useMemo(() => people.slice(0, 3), [people]);
|
const topPeople = useMemo(() => {
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const unique: BaseItemPerson[] = [];
|
||||||
|
for (const person of people) {
|
||||||
|
if (person.Id && !seen.has(person.Id)) {
|
||||||
|
seen.add(person.Id);
|
||||||
|
unique.push(person);
|
||||||
|
}
|
||||||
|
if (unique.length >= 3) break;
|
||||||
|
}
|
||||||
|
return unique;
|
||||||
|
}, [people]);
|
||||||
|
|
||||||
const renderActorSection = useCallback(
|
const renderActorSection = useCallback(
|
||||||
(person: BaseItemPerson, idx: number, total: number) => {
|
(person: BaseItemPerson, idx: number, total: number) => {
|
||||||
|
|||||||
32
hooks/useRefetchHomeOnForeground.ts
Normal file
32
hooks/useRefetchHomeOnForeground.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { AppState } from "react-native";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refetches active home queries on mount (cold start) and whenever
|
||||||
|
* the app returns to the foreground from background.
|
||||||
|
*/
|
||||||
|
export function useRefetchHomeOnForeground() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// On mount (cold start), use cancelRefetch: false so we don't cancel
|
||||||
|
// an in-flight initial fetch that React Query already started.
|
||||||
|
queryClient.refetchQueries(
|
||||||
|
{ queryKey: ["home"], type: "active" },
|
||||||
|
{ cancelRefetch: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
const subscription = AppState.addEventListener("change", (state) => {
|
||||||
|
if (state === "active") {
|
||||||
|
// On foreground return, force a fresh refetch
|
||||||
|
queryClient.refetchQueries(
|
||||||
|
{ queryKey: ["home"], type: "active" },
|
||||||
|
{ cancelRefetch: true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => subscription.remove();
|
||||||
|
}, [queryClient]);
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
"@expo/vector-icons": "^15.0.3",
|
"@expo/vector-icons": "^15.0.3",
|
||||||
"@gorhom/bottom-sheet": "5.2.8",
|
"@gorhom/bottom-sheet": "5.2.8",
|
||||||
"@jellyfin/sdk": "^0.13.0",
|
"@jellyfin/sdk": "^0.13.0",
|
||||||
"@react-native-community/netinfo": "^12.0.0",
|
"@react-native-community/netinfo": "^11.4.1",
|
||||||
"@react-navigation/material-top-tabs": "7.4.9",
|
"@react-navigation/material-top-tabs": "7.4.9",
|
||||||
"@react-navigation/native": "^7.0.14",
|
"@react-navigation/native": "^7.0.14",
|
||||||
"@shopify/flash-list": "2.0.2",
|
"@shopify/flash-list": "2.0.2",
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
"react-native-mmkv": "4.1.1",
|
"react-native-mmkv": "4.1.1",
|
||||||
"react-native-nitro-modules": "0.33.1",
|
"react-native-nitro-modules": "0.33.1",
|
||||||
"react-native-pager-view": "^6.9.1",
|
"react-native-pager-view": "^6.9.1",
|
||||||
"react-native-reanimated": "~4.4.0",
|
"react-native-reanimated": "~4.1.1",
|
||||||
"react-native-reanimated-carousel": "4.0.3",
|
"react-native-reanimated-carousel": "4.0.3",
|
||||||
"react-native-safe-area-context": "~5.6.0",
|
"react-native-safe-area-context": "~5.6.0",
|
||||||
"react-native-screens": "~4.18.0",
|
"react-native-screens": "~4.18.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user