diff --git a/android/app/build.gradle b/android/app/build.gradle index f9ed83bf..335494dd 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -94,8 +94,8 @@ android { applicationId 'com.nuvio.app' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 20 - versionName "1.2.5" + versionCode 21 + versionName "1.2.6" buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\"" } @@ -117,7 +117,7 @@ android { def abiVersionCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4] applicationVariants.all { variant -> variant.outputs.each { output -> - def baseVersionCode = 20 // Current versionCode from defaultConfig + def baseVersionCode = 21 // Current versionCode 21 from defaultConfig def abiName = output.getFilter(com.android.build.OutputFile.ABI) def versionCode = baseVersionCode * 100 // Base multiplier diff --git a/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png b/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png index 29ed05b1..15b512eb 100644 Binary files a/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png and b/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png differ diff --git a/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png b/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png index 1e3a3784..8b78e2f9 100644 Binary files a/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png and b/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png b/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png index 6b7ddf9a..528962ab 100644 Binary files a/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png and b/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png b/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png index b977b3fe..0969e6a3 100644 Binary files a/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png and b/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png b/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png index a79fe8b5..7de47821 100644 Binary files a/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png and b/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png differ diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 08f0025b..78602b85 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -3,5 +3,5 @@ contain false dark - 1.2.5 + 1.2.6 \ No newline at end of file diff --git a/app.json b/app.json index 5a069f45..8ddc14f3 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "Nuvio", "slug": "nuvio", - "version": "1.2.5", + "version": "1.2.6", "orientation": "default", "backgroundColor": "#020404", "icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png", @@ -17,7 +17,7 @@ "ios": { "supportsTablet": true, "icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png", - "buildNumber": "20", + "buildNumber": "21", "infoPlist": { "NSAppTransportSecurity": { "NSAllowsArbitraryLoads": true @@ -48,7 +48,7 @@ "WAKE_LOCK" ], "package": "com.nuvio.app", - "versionCode": 20, + "versionCode": 21, "architectures": [ "arm64-v8a", "armeabi-v7a", @@ -95,6 +95,6 @@ "fallbackToCacheTimeout": 30000, "url": "https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest" }, - "runtimeVersion": "1.2.5" + "runtimeVersion": "1.2.6" } } diff --git a/ios/Nuvio.xcodeproj/project.pbxproj b/ios/Nuvio.xcodeproj/project.pbxproj index b562e963..4bf304a6 100644 --- a/ios/Nuvio.xcodeproj/project.pbxproj +++ b/ios/Nuvio.xcodeproj/project.pbxproj @@ -460,7 +460,7 @@ "-lc++", ); OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; - PRODUCT_BUNDLE_IDENTIFIER = "com.nuvio.app"; + PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app; PRODUCT_NAME = "Nuvio"; SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; diff --git a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image.png b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image.png index efcdf22a..8649d708 100644 Binary files a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image.png and b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image.png differ diff --git a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png index efcdf22a..8649d708 100644 Binary files a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png and b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png differ diff --git a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png index efcdf22a..8649d708 100644 Binary files a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png and b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png differ diff --git a/ios/Nuvio/Info.plist b/ios/Nuvio/Info.plist index d701baa9..c4ebe4b2 100644 --- a/ios/Nuvio/Info.plist +++ b/ios/Nuvio/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.2.5 + 1.2.6 CFBundleSignature ???? CFBundleURLTypes @@ -39,7 +39,7 @@ CFBundleVersion - 20 + 21 LSMinimumSystemVersion 12.0 LSRequiresIPhoneOS diff --git a/ios/Nuvio/Supporting/Expo.plist b/ios/Nuvio/Supporting/Expo.plist index f325c178..c48927a0 100644 --- a/ios/Nuvio/Supporting/Expo.plist +++ b/ios/Nuvio/Supporting/Expo.plist @@ -9,7 +9,7 @@ EXUpdatesLaunchWaitMs 30000 EXUpdatesRuntimeVersion - 1.2.5 + 1.2.6 EXUpdatesURL https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest diff --git a/src/components/home/ContinueWatchingSection.tsx b/src/components/home/ContinueWatchingSection.tsx index 648a2c92..a9af8ac2 100644 --- a/src/components/home/ContinueWatchingSection.tsx +++ b/src/components/home/ContinueWatchingSection.tsx @@ -25,6 +25,7 @@ import * as Haptics from 'expo-haptics'; import { TraktService } from '../../services/traktService'; import { stremioService } from '../../services/stremioService'; import { streamCacheService } from '../../services/streamCacheService'; +import { useSettings } from '../../hooks/useSettings'; import CustomAlert from '../../components/CustomAlert'; // Define interface for continue watching items @@ -99,6 +100,7 @@ const isEpisodeReleased = (video: any): boolean => { const ContinueWatchingSection = React.forwardRef((props, ref) => { const navigation = useNavigation>(); const { currentTheme } = useTheme(); + const { settings } = useSettings(); const [continueWatchingItems, setContinueWatchingItems] = useState([]); const [loading, setLoading] = useState(true); const appState = useRef(AppState.currentState); @@ -656,6 +658,45 @@ const ContinueWatchingSection = React.forwardRef((props, re try { logger.log(`๐ŸŽฌ [ContinueWatching] User clicked on: ${item.name} (${item.type}:${item.id})`); + // Check if cached streams are enabled in settings + if (!settings.useCachedStreams) { + logger.log(`๐Ÿ“บ [ContinueWatching] Cached streams disabled, navigating to ${settings.openMetadataScreenWhenCacheDisabled ? 'MetadataScreen' : 'StreamsScreen'} for ${item.name}`); + + // Navigate based on the second setting + if (settings.openMetadataScreenWhenCacheDisabled) { + // Navigate to MetadataScreen + if (item.type === 'series' && item.season && item.episode) { + const episodeId = `${item.id}:${item.season}:${item.episode}`; + navigation.navigate('Metadata', { + id: item.id, + type: item.type, + episodeId: episodeId + }); + } else { + navigation.navigate('Metadata', { + id: item.id, + type: item.type + }); + } + } else { + // Navigate to StreamsScreen + if (item.type === 'series' && item.season && item.episode) { + const episodeId = `${item.id}:${item.season}:${item.episode}`; + navigation.navigate('Streams', { + id: item.id, + type: item.type, + episodeId: episodeId + }); + } else { + navigation.navigate('Streams', { + id: item.id, + type: item.type + }); + } + } + return; + } + // Check if we have a cached stream for this content const episodeId = item.type === 'series' && item.season && item.episode ? `${item.id}:${item.season}:${item.episode}` @@ -730,7 +771,7 @@ const ContinueWatchingSection = React.forwardRef((props, re }); } } - }, [navigation]); + }, [navigation, settings.useCachedStreams, settings.openMetadataScreenWhenCacheDisabled]); // Handle long press to delete (moved before renderContinueWatchingItem) const handleLongPress = useCallback((item: ContinueWatchingItem) => { diff --git a/src/components/metadata/HeroSection.tsx b/src/components/metadata/HeroSection.tsx index 45a84b69..4de55e5e 100644 --- a/src/components/metadata/HeroSection.tsx +++ b/src/components/metadata/HeroSection.tsx @@ -335,7 +335,7 @@ const ActionButtons = memo(({ return isWatched ? 'Play' : playButtonText; }, [isWatched, playButtonText, type, watchProgress, groupedEpisodes]); - // Determine if we should show buttons in a single row (Play, Save, and one other button = 3 total) + // Determine if we should show buttons in a single row (Play, Save, and optionally one other button) const hasAiChat = aiChatEnabled; const hasTraktCollection = isAuthenticated; const hasRatings = type === 'series'; @@ -343,16 +343,20 @@ const ActionButtons = memo(({ // Count additional buttons (excluding Play and Save) const additionalButtonCount = (hasAiChat ? 1 : 0) + (hasTraktCollection ? 1 : 0) + (hasRatings ? 1 : 0); - // Show single row when there's exactly 1 additional button (3 total buttons) - const shouldShowSingleRow = additionalButtonCount === 1; + // Show single row when there are 0 additional buttons (2 total: Play + Save) or 1 additional button (3 total) + const shouldShowSingleRow = additionalButtonCount <= 1; return ( {shouldShowSingleRow ? ( - /* Single Row Layout - Play, Save, and one other button (3 total) */ + /* Single Row Layout - Play, Save, and optionally one other button (2-3 total) */ @@ -370,7 +374,12 @@ const ActionButtons = memo(({ @@ -396,8 +405,8 @@ const ActionButtons = memo(({ - {/* Third Button - AI Chat, Trakt Collection, or Ratings */} - {hasAiChat && ( + {/* Third Button - AI Chat, Trakt Collection, or Ratings (only if available) */} + {hasAiChat && additionalButtonCount === 1 && ( { @@ -444,7 +453,7 @@ const ActionButtons = memo(({ )} - {hasTraktCollection && !hasAiChat && ( + {hasTraktCollection && !hasAiChat && additionalButtonCount === 1 && ( )} - {hasRatings && !hasAiChat && !hasTraktCollection && ( + {hasRatings && !hasAiChat && !hasTraktCollection && additionalButtonCount === 1 && ( ; @@ -1266,6 +1268,21 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta }, }} /> + { + const navigation = useNavigation>(); + const { settings, updateSetting } = useSettings(); + const { currentTheme } = useTheme(); + const colors = currentTheme.colors; + const [showSavedIndicator, setShowSavedIndicator] = useState(false); + const fadeAnim = React.useRef(new Animated.Value(0)).current; + + // Prevent iOS entrance flicker by restoring a non-translucent StatusBar + useEffect(() => { + try { + StatusBar.setTranslucent(false); + StatusBar.setBackgroundColor(colors.darkBackground); + StatusBar.setBarStyle('light-content'); + if (Platform.OS === 'ios') { + StatusBar.setHidden(false); + } + } catch {} + }, [colors.darkBackground]); + + const handleBack = useCallback(() => { + navigation.goBack(); + }, [navigation]); + + // Fade in/out animation for the "Changes saved" indicator + useEffect(() => { + if (showSavedIndicator) { + Animated.sequence([ + Animated.timing(fadeAnim, { + toValue: 1, + duration: 300, + useNativeDriver: true + }), + Animated.delay(1000), + Animated.timing(fadeAnim, { + toValue: 0, + duration: 300, + useNativeDriver: true + }) + ]).start(() => setShowSavedIndicator(false)); + } + }, [showSavedIndicator, fadeAnim]); + + const handleUpdateSetting = useCallback(( + key: K, + value: typeof settings[K] + ) => { + updateSetting(key, value); + setShowSavedIndicator(true); + }, [updateSetting]); + + const CustomSwitch = ({ value, onValueChange }: { value: boolean; onValueChange: (value: boolean) => void }) => ( + + ); + + const SettingItem = ({ + title, + description, + value, + onValueChange, + isLast = false + }: { + title: string; + description: string; + value: boolean; + onValueChange: (value: boolean) => void; + isLast?: boolean; + }) => ( + + + + {title} + + + {description} + + + + + ); + + const SectionHeader = ({ title }: { title: string }) => ( + + + {title} + + + ); + + return ( + + + + {/* Header */} + + + + Settings + + + Continue Watching + + + + {/* Content */} + + + + + handleUpdateSetting('useCachedStreams', value)} + isLast={!settings.useCachedStreams} + /> + {!settings.useCachedStreams && ( + handleUpdateSetting('openMetadataScreenWhenCacheDisabled', value)} + isLast={true} + /> + )} + + + + + + + How it works + + + + โ€ข Streams are cached for 1 hour after playing{'\n'} + โ€ข Cached streams are validated before use{'\n'} + โ€ข If cache is invalid or expired, falls back to content screen{'\n'} + โ€ข "Use Cached Streams" controls direct player vs screen navigation{'\n'} + โ€ข "Open Metadata Screen" appears only when cached streams are disabled + + + + + {/* Saved indicator */} + + + Changes saved + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + header: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 16, + paddingVertical: 12, + paddingTop: Platform.OS === 'ios' ? 0 : 12, + }, + backButton: { + flexDirection: 'row', + alignItems: 'center', + marginRight: 16, + }, + backText: { + fontSize: 16, + fontWeight: '600', + marginLeft: 4, + }, + headerTitle: { + fontSize: 20, + fontWeight: '700', + flex: 1, + }, + content: { + flex: 1, + }, + contentContainer: { + paddingBottom: 100, + }, + sectionHeader: { + paddingHorizontal: 16, + paddingVertical: 12, + paddingTop: 24, + }, + sectionTitle: { + fontSize: 13, + fontWeight: '700', + letterSpacing: 0.5, + textTransform: 'uppercase', + }, + settingsCard: { + marginHorizontal: 16, + borderRadius: 12, + overflow: 'hidden', + }, + settingItem: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 16, + paddingVertical: 16, + borderBottomWidth: 1, + }, + settingContent: { + flex: 1, + marginRight: 16, + }, + settingTitle: { + fontSize: 16, + fontWeight: '600', + marginBottom: 4, + }, + settingDescription: { + fontSize: 14, + lineHeight: 20, + }, + infoCard: { + marginHorizontal: 16, + marginTop: 16, + padding: 16, + borderRadius: 12, + }, + infoHeader: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 12, + }, + infoTitle: { + fontSize: 16, + fontWeight: '600', + marginLeft: 8, + }, + infoText: { + fontSize: 14, + lineHeight: 20, + }, + savedIndicator: { + position: 'absolute', + bottom: 32, + left: 16, + right: 16, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 12, + paddingHorizontal: 20, + borderRadius: 8, + }, + savedText: { + color: '#FFFFFF', + fontSize: 14, + fontWeight: '600', + marginLeft: 8, + }, +}); + +export default ContinueWatchingSettingsScreen; diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index 20b3e9dc..179d0e28 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -483,6 +483,14 @@ const SettingsScreen: React.FC = () => { icon="home" renderControl={ChevronRight} onPress={() => navigation.navigate('HomeScreenSettings')} + isTablet={isTablet} + /> + navigation.navigate('ContinueWatchingSettings')} isLast={true} isTablet={isTablet} /> diff --git a/src/screens/StreamsScreen.tsx b/src/screens/StreamsScreen.tsx index f73284a1..b8d22b60 100644 --- a/src/screens/StreamsScreen.tsx +++ b/src/screens/StreamsScreen.tsx @@ -1556,7 +1556,7 @@ export const StreamsScreen = () => { ]; }, [availableProviders, type, episodeStreams, groupedStreams, settings.streamDisplayMode]); - const sections = useMemo(() => { + const sections: Array<{ title: string; addonId: string; data: Stream[]; isEmptyDueToQualityFilter?: boolean } | null> = useMemo(() => { const streams = metadata?.videos && metadata.videos.length > 1 && selectedEpisode ? episodeStreams : groupedStreams; const installedAddons = stremioService.getInstalledAddons(); @@ -1648,12 +1648,7 @@ export const StreamsScreen = () => { const isEmptyDueToQualityFilter = totalOriginalCount > 0 && totalStreamsCount === 0; if (isEmptyDueToQualityFilter) { - return [{ - title: 'Available Streams', - addonId: 'grouped-all', - data: [{ isEmptyPlaceholder: true } as any], - isEmptyDueToQualityFilter: true - }]; + return []; // Return empty array instead of showing placeholder } // Combine streams: Addons first (unsorted), then sorted plugins @@ -1780,12 +1775,7 @@ export const StreamsScreen = () => { } if (isEmptyDueToQualityFilter) { - return { - title: addonName, - addonId, - data: [{ isEmptyPlaceholder: true } as any], - isEmptyDueToQualityFilter - }; + return null; // Return null to exclude this section completely } let processedStreams = filteredStreams; @@ -1861,7 +1851,7 @@ export const StreamsScreen = () => { }); return result; - }); + }).filter(Boolean); // Filter out null values } }, [selectedProvider, type, episodeStreams, groupedStreams, settings.streamDisplayMode, filterStreamsByQuality, addonResponseOrder, settings.streamSortMode, selectedEpisode, metadata]); @@ -1869,11 +1859,11 @@ export const StreamsScreen = () => { React.useEffect(() => { console.log('๐Ÿ” [StreamsScreen] Final sections:', { sectionsCount: sections.length, - sections: sections.map(s => ({ - title: s.title, - addonId: s.addonId, - dataCount: s.data?.length || 0, - isEmptyDueToQualityFilter: s.isEmptyDueToQualityFilter + sections: sections.filter(Boolean).map(s => ({ + title: s!.title, + addonId: s!.addonId, + dataCount: s!.data?.length || 0, + isEmptyDueToQualityFilter: s!.isEmptyDueToQualityFilter })) }); }, [sections]); @@ -2206,15 +2196,15 @@ export const StreamsScreen = () => { scrollEventThrottle: 16, })} > - {sections.map((section, sectionIndex) => ( - + {sections.filter(Boolean).map((section, sectionIndex) => ( + {/* Section Header */} - {renderSectionHeader({ section })} + {renderSectionHeader({ section: section! })} {/* Stream Cards using FlatList */} - {section.data && section.data.length > 0 ? ( + {section!.data && section!.data.length > 0 ? ( { if (item && item.url) { return `${item.url}-${sectionIndex}-${index}`; @@ -2257,20 +2247,7 @@ export const StreamsScreen = () => { index, })} /> - ) : ( - // Empty section placeholder - - - - - No streams available - - - All streams were filtered by your quality settings - - - - )} + ) : null} ))} @@ -2805,28 +2782,6 @@ const createStyles = (colors: any) => StyleSheet.create({ fontSize: 11, fontWeight: '400', }, - emptySectionContainer: { - padding: 16, - alignItems: 'center', - justifyContent: 'center', - minHeight: 80, - }, - emptySectionContent: { - alignItems: 'center', - justifyContent: 'center', - }, - emptySectionTitle: { - fontSize: 14, - fontWeight: '600', - marginTop: 8, - textAlign: 'center', - }, - emptySectionSubtitle: { - fontSize: 12, - marginTop: 4, - textAlign: 'center', - lineHeight: 16, - }, }); export default memo(StreamsScreen); diff --git a/src/services/streamCacheService.ts b/src/services/streamCacheService.ts index d4487117..be658be3 100644 --- a/src/services/streamCacheService.ts +++ b/src/services/streamCacheService.ts @@ -92,14 +92,9 @@ class StreamCacheService { return null; } - // Validate that the stream URL is still accessible (quick HEAD request) - logger.log(`๐Ÿ” [StreamCache] Validating stream URL: ${cacheEntry.cachedStream.url}`); - const isUrlValid = await this.validateStreamUrl(cacheEntry.cachedStream.url); - if (!isUrlValid) { - logger.log(`โŒ [StreamCache] Stream URL invalid for ${type}:${id}${episodeId ? `:${episodeId}` : ''}`); - await this.removeCachedStream(id, type, episodeId); - return null; - } + // Skip URL validation for now - many CDNs block HEAD requests + // This was causing valid streams to be rejected + logger.log(`๐Ÿ” [StreamCache] Skipping URL validation (CDN compatibility)`); logger.log(`โœ… [StreamCache] Using cached stream for ${type}:${id}${episodeId ? `:${episodeId}` : ''}`); return cacheEntry.cachedStream; @@ -131,7 +126,7 @@ class StreamCacheService { const cacheKeys = allKeys.filter(key => key.startsWith(CACHE_KEY_PREFIX)); for (const key of cacheKeys) { - await storageService.removeItem(key); + await AsyncStorage.removeItem(key); } logger.log(`๐Ÿงน [StreamCache] Cleared ${cacheKeys.length} cached streams`); @@ -173,8 +168,8 @@ class StreamCacheService { */ async getCacheInfo(): Promise<{ totalCached: number; expiredCount: number; validCount: number }> { try { - const allKeys = await storageService.getAllKeys(); - const cacheKeys = allKeys.filter(key => key.startsWith(CACHE_KEY_PREFIX)); + const allKeys = await AsyncStorage.getAllKeys(); + const cacheKeys = allKeys.filter((key: string) => key.startsWith(CACHE_KEY_PREFIX)); let expiredCount = 0; let validCount = 0; @@ -182,7 +177,7 @@ class StreamCacheService { for (const key of cacheKeys) { try { - const cachedData = await storageService.getItem(key); + const cachedData = await AsyncStorage.getItem(key); if (cachedData) { const cacheEntry: StreamCacheEntry = JSON.parse(cachedData); if (now > cacheEntry.expiresAt) { diff --git a/src/utils/version.ts b/src/utils/version.ts index f29182b7..df5a2ca8 100644 --- a/src/utils/version.ts +++ b/src/utils/version.ts @@ -1,7 +1,7 @@ // Single source of truth for the app version displayed in Settings // Update this when bumping app version -export const APP_VERSION = '1.2.5'; +export const APP_VERSION = '1.2.6'; export function getDisplayedAppVersion(): string { return APP_VERSION;