Compare commits

...

6 Commits

Author SHA1 Message Date
sarendsen
343a2cb3b5 wip 2025-03-01 18:40:43 +01:00
sarendsen
49d1be6bdf wip 2025-03-01 16:07:24 +01:00
sarendsen
32687332b3 wip 2025-03-01 14:24:53 +01:00
sarendsen
7c96443532 wip 2025-03-01 14:23:13 +01:00
sarendsen
bf8728eeab wip 2025-03-01 14:20:52 +01:00
sarendsen
d7ae41907e feat: Add session count to app badge 2025-03-01 14:07:18 +01:00
5 changed files with 91 additions and 56 deletions

View File

@@ -2,30 +2,26 @@ import "@/augmentations";
import { Platform } from "react-native"; import { Platform } from "react-native";
import i18n from "@/i18n"; import i18n from "@/i18n";
import { DownloadProvider } from "@/providers/DownloadProvider"; import { DownloadProvider } from "@/providers/DownloadProvider";
import { import { getOrSetDeviceId, getTokenFromStorage, JellyfinProvider, apiAtom } from "@/providers/JellyfinProvider";
getOrSetDeviceId,
getTokenFromStorage,
JellyfinProvider,
} from "@/providers/JellyfinProvider";
import { JobQueueProvider } from "@/providers/JobQueueProvider"; import { JobQueueProvider } from "@/providers/JobQueueProvider";
import { PlaySettingsProvider } from "@/providers/PlaySettingsProvider"; import { PlaySettingsProvider } from "@/providers/PlaySettingsProvider";
import { WebSocketProvider } from "@/providers/WebSocketProvider"; import { WebSocketProvider } from "@/providers/WebSocketProvider";
import { Settings, useSettings } from "@/utils/atoms/settings"; import { Settings, useSettings } from "@/utils/atoms/settings";
import { BACKGROUND_FETCH_TASK } from "@/utils/background-tasks"; import {
BACKGROUND_FETCH_TASK,
BACKGROUND_FETCH_TASK_SESSIONS,
registerBackgroundFetchAsyncSessions,
} from "@/utils/background-tasks";
import { LogProvider, writeToLog } from "@/utils/log"; import { LogProvider, writeToLog } from "@/utils/log";
import { storage } from "@/utils/mmkv"; import { storage } from "@/utils/mmkv";
import { cancelJobById, getAllJobsByDeviceId } from "@/utils/optimize-server"; import { cancelJobById, getAllJobsByDeviceId } from "@/utils/optimize-server";
import { ActionSheetProvider } from "@expo/react-native-action-sheet"; import { ActionSheetProvider } from "@expo/react-native-action-sheet";
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet"; import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
const BackGroundDownloader = !Platform.isTV const BackGroundDownloader = !Platform.isTV ? require("@kesha-antonov/react-native-background-downloader") : null;
? require("@kesha-antonov/react-native-background-downloader")
: null;
import { DarkTheme, ThemeProvider } from "@react-navigation/native"; import { DarkTheme, ThemeProvider } from "@react-navigation/native";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const BackgroundFetch = !Platform.isTV const BackgroundFetch = !Platform.isTV ? require("expo-background-fetch") : null;
? require("expo-background-fetch")
: null;
import * as FileSystem from "expo-file-system"; import * as FileSystem from "expo-file-system";
const Notifications = !Platform.isTV ? require("expo-notifications") : null; const Notifications = !Platform.isTV ? require("expo-notifications") : null;
import { router, Stack } from "expo-router"; import { router, Stack } from "expo-router";
@@ -41,6 +37,10 @@ import { SystemBars } from "react-native-edge-to-edge";
import { GestureHandlerRootView } from "react-native-gesture-handler"; import { GestureHandlerRootView } from "react-native-gesture-handler";
import "react-native-reanimated"; import "react-native-reanimated";
import { Toaster } from "sonner-native"; import { Toaster } from "sonner-native";
import { useAtom } from "jotai";
import { userAtom } from "@/providers/JellyfinProvider";
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api/session-api";
import { store } from "@/utils/store";
if (!Platform.isTV) { if (!Platform.isTV) {
Notifications.setNotificationHandler({ Notifications.setNotificationHandler({
@@ -74,20 +74,16 @@ function useNotificationObserver() {
} }
} }
Notifications.getLastNotificationResponseAsync().then( Notifications.getLastNotificationResponseAsync().then((response: { notification: any }) => {
(response: { notification: any }) => { if (!isMounted || !response?.notification) {
if (!isMounted || !response?.notification) { return;
return;
}
redirect(response?.notification);
} }
); redirect(response?.notification);
});
const subscription = Notifications.addNotificationResponseReceivedListener( const subscription = Notifications.addNotificationResponseReceivedListener((response: { notification: any }) => {
(response: { notification: any }) => { redirect(response.notification);
redirect(response.notification); });
}
);
return () => { return () => {
isMounted = false; isMounted = false;
@@ -97,6 +93,22 @@ function useNotificationObserver() {
} }
if (!Platform.isTV) { if (!Platform.isTV) {
TaskManager.defineTask(BACKGROUND_FETCH_TASK_SESSIONS, async () => {
console.log("TaskManager ~ sessions trigger");
const api = store.get(apiAtom);
if (api === null || api === undefined) return;
const response = await getSessionApi(api).getSessions({
activeWithinSeconds: 360,
});
const result = response.data.filter((s) => s.NowPlayingItem);
Notifications.setBadgeCountAsync(result.length);
return BackgroundFetch.BackgroundFetchResult.NewData;
});
TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => { TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
console.log("TaskManager ~ trigger"); console.log("TaskManager ~ trigger");
@@ -109,15 +121,13 @@ if (!Platform.isTV) {
const settings: Partial<Settings> = JSON.parse(settingsData); const settings: Partial<Settings> = JSON.parse(settingsData);
const url = settings?.optimizedVersionsServerUrl; const url = settings?.optimizedVersionsServerUrl;
if (!settings?.autoDownload || !url) if (!settings?.autoDownload || !url) return BackgroundFetch.BackgroundFetchResult.NoData;
return BackgroundFetch.BackgroundFetchResult.NoData;
const token = getTokenFromStorage(); const token = getTokenFromStorage();
const deviceId = getOrSetDeviceId(); const deviceId = getOrSetDeviceId();
const baseDirectory = FileSystem.documentDirectory; const baseDirectory = FileSystem.documentDirectory;
if (!token || !deviceId || !baseDirectory) if (!token || !deviceId || !baseDirectory) return BackgroundFetch.BackgroundFetchResult.NoData;
return BackgroundFetch.BackgroundFetchResult.NoData;
const jobs = await getAllJobsByDeviceId({ const jobs = await getAllJobsByDeviceId({
deviceId, deviceId,
@@ -194,9 +204,7 @@ if (!Platform.isTV) {
const checkAndRequestPermissions = async () => { const checkAndRequestPermissions = async () => {
try { try {
const hasAskedBefore = storage.getString( const hasAskedBefore = storage.getString("hasAskedForNotificationPermission");
"hasAskedForNotificationPermission"
);
if (hasAskedBefore !== "true") { if (hasAskedBefore !== "true") {
const { status } = await Notifications.requestPermissionsAsync(); const { status } = await Notifications.requestPermissionsAsync();
@@ -214,11 +222,7 @@ const checkAndRequestPermissions = async () => {
console.log("Already asked for notification permissions before."); console.log("Already asked for notification permissions before.");
} }
} catch (error) { } catch (error) {
writeToLog( writeToLog("ERROR", "Error checking/requesting notification permissions:", error);
"ERROR",
"Error checking/requesting notification permissions:",
error
);
console.error("Error checking/requesting notification permissions:", error); console.error("Error checking/requesting notification permissions:", error);
} }
}; };
@@ -253,12 +257,11 @@ const queryClient = new QueryClient({
function Layout() { function Layout() {
const [settings] = useSettings(); const [settings] = useSettings();
const [user] = useAtom(userAtom);
const appState = useRef(AppState.currentState); const appState = useRef(AppState.currentState);
useEffect(() => { useEffect(() => {
i18n.changeLanguage( i18n.changeLanguage(settings?.preferedLanguage ?? getLocales()[0].languageCode ?? "en");
settings?.preferedLanguage ?? getLocales()[0].languageCode ?? "en"
);
}, [settings?.preferedLanguage, i18n]); }, [settings?.preferedLanguage, i18n]);
if (!Platform.isTV) { if (!Platform.isTV) {
@@ -266,6 +269,11 @@ function Layout() {
useEffect(() => { useEffect(() => {
checkAndRequestPermissions(); checkAndRequestPermissions();
(async () => {
if (!Platform.isTV && user && user.Policy?.IsAdministrator) {
registerBackgroundFetchAsyncSessions();
}
})();
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -275,24 +283,16 @@ function Layout() {
ScreenOrientation.unlockAsync(); ScreenOrientation.unlockAsync();
} else { } else {
// If the user has auto rotate disabled, lock the orientation to portrait // If the user has auto rotate disabled, lock the orientation to portrait
ScreenOrientation.lockAsync( ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP);
ScreenOrientation.OrientationLock.PORTRAIT_UP
);
} }
}, [settings]); }, [settings]);
useEffect(() => { useEffect(() => {
const subscription = AppState.addEventListener( const subscription = AppState.addEventListener("change", (nextAppState) => {
"change", if (appState.current.match(/inactive|background/) && nextAppState === "active") {
(nextAppState) => { BackGroundDownloader.checkForExistingDownloads();
if (
appState.current.match(/inactive|background/) &&
nextAppState === "active"
) {
BackGroundDownloader.checkForExistingDownloads();
}
} }
); });
BackGroundDownloader.checkForExistingDownloads(); BackGroundDownloader.checkForExistingDownloads();
@@ -369,9 +369,7 @@ function Layout() {
function saveDownloadedItemInfo(item: BaseItemDto) { function saveDownloadedItemInfo(item: BaseItemDto) {
try { try {
const downloadedItems = storage.getString("downloadedItems"); const downloadedItems = storage.getString("downloadedItems");
let items: BaseItemDto[] = downloadedItems let items: BaseItemDto[] = downloadedItems ? JSON.parse(downloadedItems) : [];
? JSON.parse(downloadedItems)
: [];
const existingItemIndex = items.findIndex((i) => i.Id === item.Id); const existingItemIndex = items.findIndex((i) => i.Id === item.Id);
if (existingItemIndex !== -1) { if (existingItemIndex !== -1) {

View File

@@ -3,6 +3,8 @@ import { apiAtom } from "@/providers/JellyfinProvider";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { getSessionApi } from "@jellyfin/sdk/lib/utils/api/session-api"; import { getSessionApi } from "@jellyfin/sdk/lib/utils/api/session-api";
import { userAtom } from "@/providers/JellyfinProvider"; import { userAtom } from "@/providers/JellyfinProvider";
import { Platform } from "react-native";
const Notifications = !Platform.isTV ? require("expo-notifications") : null;
export interface useSessionsProps { export interface useSessionsProps {
refetchInterval: number; refetchInterval: number;
@@ -22,9 +24,13 @@ export const useSessions = ({ refetchInterval = 5 * 1000, activeWithinSeconds =
const response = await getSessionApi(api).getSessions({ const response = await getSessionApi(api).getSessions({
activeWithinSeconds: activeWithinSeconds, activeWithinSeconds: activeWithinSeconds,
}); });
return response.data
const result = response.data
.filter((s) => s.NowPlayingItem) .filter((s) => s.NowPlayingItem)
.sort((a, b) => (b.NowPlayingItem?.Name ?? "").localeCompare(a.NowPlayingItem?.Name ?? "")); .sort((a, b) => (b.NowPlayingItem?.Name ?? "").localeCompare(a.NowPlayingItem?.Name ?? ""));
Notifications.setBadgeCountAsync(result.length);
return result
}, },
refetchInterval: refetchInterval, refetchInterval: refetchInterval,
}); });

View File

@@ -3,6 +3,7 @@ import { useInterval } from "@/hooks/useInterval";
import { JellyseerrApi, useJellyseerr } from "@/hooks/useJellyseerr"; import { JellyseerrApi, useJellyseerr } from "@/hooks/useJellyseerr";
import { useSettings } from "@/utils/atoms/settings"; import { useSettings } from "@/utils/atoms/settings";
import { storage } from "@/utils/mmkv"; import { storage } from "@/utils/mmkv";
import { store } from "@/utils/store";
import { Api, Jellyfin } from "@jellyfin/sdk"; import { Api, Jellyfin } from "@jellyfin/sdk";
import { UserDto } from "@jellyfin/sdk/lib/generated-client/models"; import { UserDto } from "@jellyfin/sdk/lib/generated-client/models";
import { getUserApi } from "@jellyfin/sdk/lib/utils/api"; import { getUserApi } from "@jellyfin/sdk/lib/utils/api";
@@ -165,6 +166,10 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
await refreshStreamyfinPluginSettings(); await refreshStreamyfinPluginSettings();
})(); })();
}, []); }, []);
useEffect(() => {
store.set(apiAtom, api);
}, [api]);
useInterval(pollQuickConnect, isPolling ? 1000 : null); useInterval(pollQuickConnect, isPolling ? 1000 : null);
useInterval(refreshStreamyfinPluginSettings, 60 * 5 * 1000); // 5 min useInterval(refreshStreamyfinPluginSettings, 60 * 5 * 1000); // 5 min

View File

@@ -24,3 +24,26 @@ export async function unregisterBackgroundFetchAsync() {
console.log("Error unregistering background fetch task", error); console.log("Error unregistering background fetch task", error);
} }
} }
export const BACKGROUND_FETCH_TASK_SESSIONS =
"background-fetch-sessions";
export async function registerBackgroundFetchAsyncSessions() {
try {
BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK_SESSIONS, {
minimumInterval: 1 * 60, // 1 minutes
stopOnTerminate: false, // android only,
startOnBoot: true, // android only
});
} catch (error) {
console.log("Error registering background fetch task", error);
}
}
export async function unregisterBackgroundFetchAsyncSessions() {
try {
BackgroundFetch.unregisterTaskAsync(BACKGROUND_FETCH_TASK_SESSIONS);
} catch (error) {
console.log("Error unregistering background fetch task", error);
}
}

3
utils/store.ts Normal file
View File

@@ -0,0 +1,3 @@
import { createStore } from 'jotai';
export const store = createStore();