From 7a0ac95164a59436b8a1d9fd63463df9166b5338 Mon Sep 17 00:00:00 2001 From: Ritesh Shukla Date: Fri, 29 Aug 2025 22:23:36 +0530 Subject: [PATCH] Migrate Network Status to Zustand Store (#499) Moves the network status state to a Zustand Store for better reusability and to reduce the complexity of the network context No new user facing changes --- index.js | 2 + src/components/Album/index.tsx | 4 +- src/components/Artist/tab-bar.tsx | 4 +- src/components/Context/index.tsx | 3 +- src/components/Global/components/item-row.tsx | 4 +- src/components/Global/components/track.tsx | 4 +- .../Home/helpers/frequent-tracks.tsx | 4 +- .../Home/helpers/recently-played.tsx | 4 +- .../Network/internetConnectionWatcher.tsx | 4 +- src/components/Playlist/components/header.tsx | 3 +- src/components/Storage/index.tsx | 2 +- src/components/Tracks/component.tsx | 1 - src/providers/CarPlay/index.tsx | 4 +- src/providers/Network/index.tsx | 8 --- src/screens/Settings/sign-out-modal.tsx | 1 - src/stores/network.ts | 30 +++++++++++ src/utils/console-override.ts | 54 +++++++++++++++++++ 17 files changed, 108 insertions(+), 28 deletions(-) create mode 100644 src/stores/network.ts create mode 100644 src/utils/console-override.ts diff --git a/index.js b/index.js index fe6f9074..1f5312b5 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,6 @@ import 'react-native-gesture-handler' +// Initialize console override early - disable all console methods in production +import './src/utils/console-override' import { AppRegistry } from 'react-native' import App from './App' import { name as appName } from './app.json' diff --git a/src/components/Album/index.tsx b/src/components/Album/index.tsx index 8702e6b7..d94b78a2 100644 --- a/src/components/Album/index.tsx +++ b/src/components/Album/index.tsx @@ -15,6 +15,7 @@ import { useSafeAreaFrame } from 'react-native-safe-area-context' import Icon from '../Global/components/icon' import { mapDtoToTrack } from '../../utils/mappings' import { useNetworkContext } from '../../providers/Network' +import { useNetworkStatus } from '../../stores/network' import { useLoadNewQueue } from '../../providers/Player/hooks/mutations' import { QueuingType } from '../../enums/queuing-type' import { useAlbumContext } from '../../providers/Album' @@ -40,7 +41,8 @@ export function Album(): React.JSX.Element { const { album, discs, isPending } = useAlbumContext() const { api } = useJellifyContext() - const { useDownloadMultiple, pendingDownloads, networkStatus } = useNetworkContext() + const { useDownloadMultiple, pendingDownloads } = useNetworkContext() + const [networkStatus] = useNetworkStatus() const streamingDeviceProfile = useStreamingDeviceProfile() const downloadingDeviceProfile = useDownloadingDeviceProfile() const { mutate: loadNewQueue } = useLoadNewQueue() diff --git a/src/components/Artist/tab-bar.tsx b/src/components/Artist/tab-bar.tsx index 145247f2..030c2e3b 100644 --- a/src/components/Artist/tab-bar.tsx +++ b/src/components/Artist/tab-bar.tsx @@ -17,7 +17,7 @@ import { QueuingType } from '../../enums/queuing-type' import { fetchAlbumDiscs } from '../../api/queries/item' import { NativeStackNavigationProp } from '@react-navigation/native-stack' import { BaseStackParamList } from '../../screens/types' -import { useNetworkContext } from '../../providers/Network' +import { useNetworkStatus } from '../../stores/network' import useStreamingDeviceProfile from '../../stores/device-profile' export default function ArtistTabBar({ @@ -33,7 +33,7 @@ export default function ArtistTabBar({ const deviceProfile = useStreamingDeviceProfile() - const { networkStatus } = useNetworkContext() + const [networkStatus] = useNetworkStatus() const { width } = useSafeAreaFrame() diff --git a/src/components/Context/index.tsx b/src/components/Context/index.tsx index bd25dcaf..92fb1aee 100644 --- a/src/components/Context/index.tsx +++ b/src/components/Context/index.tsx @@ -27,6 +27,7 @@ import { TextTickerConfig } from '../Player/component.config' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { trigger } from 'react-native-haptic-feedback' import { useAddToQueue } from '../../providers/Player/hooks/mutations' +import { useNetworkStatus } from '../../stores/network' import { useNetworkContext } from '../../providers/Network' import { mapDtoToTrack } from '../../utils/mappings' import useStreamingDeviceProfile, { useDownloadingDeviceProfile } from '../../stores/device-profile' @@ -164,7 +165,7 @@ function AddToPlaylistRow({ track }: { track: BaseItemDto }): React.JSX.Element function AddToQueueMenuRow({ tracks }: { tracks: BaseItemDto[] }): React.JSX.Element { const { api } = useJellifyContext() - const { networkStatus } = useNetworkContext() + const [networkStatus] = useNetworkStatus() const deviceProfile = useStreamingDeviceProfile() diff --git a/src/components/Global/components/item-row.tsx b/src/components/Global/components/item-row.tsx index aebf1cc3..b414a607 100644 --- a/src/components/Global/components/item-row.tsx +++ b/src/components/Global/components/item-row.tsx @@ -13,7 +13,7 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack' import { BaseStackParamList } from '../../../screens/types' import { useLoadNewQueue } from '../../../providers/Player/hooks/mutations' import { useJellifyContext } from '../../../providers' -import { useNetworkContext } from '../../../providers/Network' +import { useNetworkStatus } from '../../../stores/network' import useStreamingDeviceProfile from '../../../stores/device-profile' interface ItemRowProps { @@ -43,7 +43,7 @@ export default function ItemRow({ }: ItemRowProps): React.JSX.Element { const { api } = useJellifyContext() - const { networkStatus } = useNetworkContext() + const [networkStatus] = useNetworkStatus() const deviceProfile = useStreamingDeviceProfile() diff --git a/src/components/Global/components/track.tsx b/src/components/Global/components/track.tsx index 40fa8f11..682a6d50 100644 --- a/src/components/Global/components/track.tsx +++ b/src/components/Global/components/track.tsx @@ -8,7 +8,7 @@ import { QueuingType } from '../../../enums/queuing-type' import { Queue } from '../../../player/types/queue-item' import FavoriteIcon from './favorite-icon' import { networkStatusTypes } from '../../../components/Network/internetConnectionWatcher' -import { useNetworkContext } from '../../../providers/Network' +import { useNetworkStatus } from '../../../stores/network' import DownloadedIcon from './downloaded-icon' import navigationRef from '../../../../navigation' import { NativeStackNavigationProp } from '@react-navigation/native-stack' @@ -63,7 +63,7 @@ export default function Track({ const { data: nowPlaying } = useNowPlaying() const { data: playQueue } = useQueue() const { mutate: loadNewQueue } = useLoadNewQueue() - const { networkStatus } = useNetworkContext() + const [networkStatus] = useNetworkStatus() const { data: mediaInfo } = useStreamedMediaInfo(track.Id) diff --git a/src/components/Home/helpers/frequent-tracks.tsx b/src/components/Home/helpers/frequent-tracks.tsx index cc218cb9..d52ffc9e 100644 --- a/src/components/Home/helpers/frequent-tracks.tsx +++ b/src/components/Home/helpers/frequent-tracks.tsx @@ -12,13 +12,13 @@ import HomeStackParamList from '../../../screens/Home/types' import { useNavigation } from '@react-navigation/native' import { RootStackParamList } from '../../../screens/types' import { useJellifyContext } from '../../../providers' -import { useNetworkContext } from '../../../providers/Network' +import { useNetworkStatus } from '../../../stores/network' import useStreamingDeviceProfile from '../../../stores/device-profile' export default function FrequentlyPlayedTracks(): React.JSX.Element { const { api } = useJellifyContext() - const { networkStatus } = useNetworkContext() + const [networkStatus] = useNetworkStatus() const deviceProfile = useStreamingDeviceProfile() diff --git a/src/components/Home/helpers/recently-played.tsx b/src/components/Home/helpers/recently-played.tsx index 9b61c549..5b644ea6 100644 --- a/src/components/Home/helpers/recently-played.tsx +++ b/src/components/Home/helpers/recently-played.tsx @@ -14,13 +14,13 @@ import { useNavigation } from '@react-navigation/native' import HomeStackParamList from '../../../screens/Home/types' import { useNowPlaying } from '../../../providers/Player/hooks/queries' import { useJellifyContext } from '../../../providers' -import { useNetworkContext } from '../../../providers/Network' +import { useNetworkStatus } from '../../../stores/network' import useStreamingDeviceProfile from '../../../stores/device-profile' export default function RecentlyPlayed(): React.JSX.Element { const { api } = useJellifyContext() - const { networkStatus } = useNetworkContext() + const [networkStatus] = useNetworkStatus() const deviceProfile = useStreamingDeviceProfile() diff --git a/src/components/Network/internetConnectionWatcher.tsx b/src/components/Network/internetConnectionWatcher.tsx index 7c0d43a0..9c3bbe10 100644 --- a/src/components/Network/internetConnectionWatcher.tsx +++ b/src/components/Network/internetConnectionWatcher.tsx @@ -11,7 +11,7 @@ import Animated, { } from 'react-native-reanimated' import { Text } from '../Global/helpers/text' -import { useNetworkContext } from '../../providers/Network' +import { useNetworkStatus } from '../../stores/network' const internetConnectionWatcher = { NO_INTERNET: 'You are offline', @@ -28,7 +28,7 @@ const isAndroid = Platform.OS === 'android' const InternetConnectionWatcher = () => { // const [networkStatus, setNetworkStatus] = useState(null) const lastNetworkStatus = useRef(networkStatusTypes.ONLINE) - const { networkStatus, setNetworkStatus } = useNetworkContext() + const [networkStatus, setNetworkStatus] = useNetworkStatus() const bannerHeight = useSharedValue(0) const opacity = useSharedValue(0) diff --git a/src/components/Playlist/components/header.tsx b/src/components/Playlist/components/header.tsx index 8d6bb66e..2f192bb3 100644 --- a/src/components/Playlist/components/header.tsx +++ b/src/components/Playlist/components/header.tsx @@ -11,6 +11,7 @@ import FastImage from 'react-native-fast-image' import { getImageApi } from '@jellyfin/sdk/lib/utils/api' import { useJellifyContext } from '../../../providers' import { ImageType } from '@jellyfin/sdk/lib/generated-client/models' +import { useNetworkStatus } from '../../../../src/stores/network' import { useNetworkContext } from '../../../../src/providers/Network' import { ActivityIndicator } from 'react-native' import { mapDtoToTrack } from '../../../utils/mappings' @@ -156,7 +157,7 @@ function PlaylistHeaderControls({ const isDownloading = pendingDownloads.length != 0 const { api } = useJellifyContext() - const { networkStatus } = useNetworkContext() + const [networkStatus] = useNetworkStatus() const navigation = useNavigation>() diff --git a/src/components/Storage/index.tsx b/src/components/Storage/index.tsx index 3d61ae4b..a948df94 100644 --- a/src/components/Storage/index.tsx +++ b/src/components/Storage/index.tsx @@ -3,8 +3,8 @@ import { StyleSheet, Pressable, Alert, FlatList } from 'react-native' import RNFS from 'react-native-fs' import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated' import { deleteAudioCache } from '../../api/mutations/download/offlineModeUtils' -import { useNetworkContext } from '../../providers/Network' import Icon from '../Global/components/icon' +import { useNetworkContext } from '../../providers/Network' import { getToken, View } from 'tamagui' import { Text } from '../Global/helpers/text' import { useAllDownloadedTracks } from '../../api/queries/download' diff --git a/src/components/Tracks/component.tsx b/src/components/Tracks/component.tsx index ca10ba7e..df499cb2 100644 --- a/src/components/Tracks/component.tsx +++ b/src/components/Tracks/component.tsx @@ -3,7 +3,6 @@ import Track from '../Global/components/track' import { getTokens, Separator } from 'tamagui' import { BaseItemDto, UserItemDataDto } from '@jellyfin/sdk/lib/generated-client/models' import { Queue } from '../../player/types/queue-item' -import { useNetworkContext } from '../../providers/Network' import { queryClient } from '../../constants/query-client' import { QueryKeys } from '../../enums/query-keys' import { FlashList, ViewToken } from '@shopify/flash-list' diff --git a/src/providers/CarPlay/index.tsx b/src/providers/CarPlay/index.tsx index 7d8a5aec..e21f8405 100644 --- a/src/providers/CarPlay/index.tsx +++ b/src/providers/CarPlay/index.tsx @@ -4,7 +4,7 @@ import { Platform } from 'react-native' import { CarPlay } from 'react-native-carplay' import { useJellifyContext } from '../index' import { useLoadNewQueue } from '../Player/hooks/mutations' -import { useNetworkContext } from '../Network' +import { useNetworkStatus } from '../../stores/network' import useStreamingDeviceProfile from '../../stores/device-profile' interface CarPlayContext { @@ -15,7 +15,7 @@ const CarPlayContextInitializer = () => { const { api, library } = useJellifyContext() const [carplayConnected, setCarPlayConnected] = useState(CarPlay ? CarPlay.connected : false) - const { networkStatus } = useNetworkContext() + const [networkStatus] = useNetworkStatus() const deviceProfile = useStreamingDeviceProfile() diff --git a/src/providers/Network/index.tsx b/src/providers/Network/index.tsx index 7edcf5fc..bab954e5 100644 --- a/src/providers/Network/index.tsx +++ b/src/providers/Network/index.tsx @@ -9,8 +9,6 @@ import { useAllDownloadedTracks } from '../../api/queries/download' interface NetworkContext { useDownloadMultiple: UseMutateFunction activeDownloads: JellifyDownloadProgress | undefined - networkStatus: networkStatusTypes | null - setNetworkStatus: (status: networkStatusTypes | null) => void pendingDownloads: JellifyTrack[] downloadingDownloads: JellifyTrack[] completedDownloads: JellifyTrack[] @@ -20,7 +18,6 @@ interface NetworkContext { const MAX_CONCURRENT_DOWNLOADS = 1 const NetworkContextInitializer = () => { const [downloadProgress, setDownloadProgress] = useState({}) - const [networkStatus, setNetworkStatus] = useState(null) // Mutiple Downloads const [pending, setPending] = useState([]) @@ -76,8 +73,6 @@ const NetworkContextInitializer = () => { return { activeDownloads: downloadProgress, downloadedTracks, - networkStatus, - setNetworkStatus, useDownloadMultiple, pendingDownloads: pending, downloadingDownloads: downloading, @@ -88,8 +83,6 @@ const NetworkContextInitializer = () => { const NetworkContext = createContext({ activeDownloads: {}, - networkStatus: networkStatusTypes.ONLINE, - setNetworkStatus: () => {}, useDownloadMultiple: () => {}, pendingDownloads: [], downloadingDownloads: [], @@ -109,7 +102,6 @@ export const NetworkContextProvider: ({ () => context, [ context.downloadedTracks?.length, - context.networkStatus, context.pendingDownloads.length, context.downloadingDownloads.length, context.completedDownloads.length, diff --git a/src/screens/Settings/sign-out-modal.tsx b/src/screens/Settings/sign-out-modal.tsx index a885aed4..5dcea994 100644 --- a/src/screens/Settings/sign-out-modal.tsx +++ b/src/screens/Settings/sign-out-modal.tsx @@ -4,7 +4,6 @@ import { H5, Text } from '../../components/Global/helpers/text' import Button from '../../components/Global/helpers/button' import Icon from '../../components/Global/components/icon' import { useJellifyContext } from '../../providers' -import { useNetworkContext } from '../../providers/Network' import { useResetQueue } from '../../providers/Player/hooks/mutations' import navigationRef from '../../../navigation' import { useClearAllDownloads } from '../../api/mutations/download' diff --git a/src/stores/network.ts b/src/stores/network.ts new file mode 100644 index 00000000..4a995269 --- /dev/null +++ b/src/stores/network.ts @@ -0,0 +1,30 @@ +import { create } from 'zustand' +import { devtools } from 'zustand/middleware' +import { networkStatusTypes } from '../components/Network/internetConnectionWatcher' + +type NetworkStore = { + networkStatus: networkStatusTypes | null + setNetworkStatus: (status: networkStatusTypes | null) => void +} + +export const useNetworkStore = create()( + devtools( + (set) => ({ + networkStatus: null, + setNetworkStatus: (networkStatus) => set({ networkStatus }), + }), + { + name: 'network-store', + }, + ), +) + +export const useNetworkStatus = (): [ + networkStatusTypes | null, + (status: networkStatusTypes | null) => void, +] => { + const networkStatus = useNetworkStore((state) => state.networkStatus) + const setNetworkStatus = useNetworkStore((state) => state.setNetworkStatus) + + return [networkStatus, setNetworkStatus] +} diff --git a/src/utils/console-override.ts b/src/utils/console-override.ts new file mode 100644 index 00000000..8e7f46c9 --- /dev/null +++ b/src/utils/console-override.ts @@ -0,0 +1,54 @@ +/** + * Global Console Override for Production Builds + * + * This utility disables ALL console methods in production builds by replacing them with no-op functions. + * In development, console methods work normally. + * + * Usage: Import this file early in your app's entry point (index.js) + * + * Benefits: + * - No need to modify existing console.log statements throughout the codebase + * - Catches all console usage (log, warn, error, info, debug, etc.) + * - Reduces bundle size and improves performance in production + * - Prevents sensitive debug information from appearing in production + */ + +// No-op function for production console methods +const noop = () => {} + +/** + * Initialize console override + * Disables all console methods in production builds (__DEV__ = false) + * Preserves console methods in development builds (__DEV__ = true) + */ +export const initializeConsoleOverride = () => { + if (!__DEV__) { + // Production: Replace all console methods with no-op functions + console.log = noop + console.warn = noop + console.error = noop + console.info = noop + console.debug = noop + console.trace = noop + console.table = noop + console.group = noop + console.groupCollapsed = noop + console.groupEnd = noop + console.time = noop + console.timeEnd = noop + console.timeLog = noop + console.count = noop + console.countReset = noop + console.assert = noop + console.clear = noop + console.dir = noop + console.dirxml = noop + console.profile = noop + console.profileEnd = noop + console.timeStamp = noop + } + // In development (__DEV__ = true), console methods remain unchanged +} + +// Auto-initialize when this module is imported +initializeConsoleOverride()