From 837decd9bdc818aa8f65b6ab19b77104403b93db Mon Sep 17 00:00:00 2001
From: tapframe <85391825+tapframe@users.noreply.github.com>
Date: Fri, 13 Mar 2026 09:08:02 +0530
Subject: [PATCH] update version
---
android/app/build.gradle | 6 +-
android/app/src/main/res/values/strings.xml | 2 +-
app.json | 8 +-
ios/Nuvio/Info.plist | 4 +-
src/contexts/DownloadsContext.tsx | 83 ++++++++++++++++++---
src/utils/version.ts | 2 +-
6 files changed, 83 insertions(+), 22 deletions(-)
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 1ca68471..c490dc33 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -95,8 +95,8 @@ android {
applicationId 'com.nuvio.app'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 37
- versionName "1.4.1"
+ versionCode 38
+ versionName "1.4.2"
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
}
@@ -118,7 +118,7 @@ android {
def abiVersionCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
applicationVariants.all { variant ->
variant.outputs.each { output ->
- def baseVersionCode = 37 // Current versionCode 37 from defaultConfig
+ def baseVersionCode = 38 // Current versionCode 38 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/values/strings.xml b/android/app/src/main/res/values/strings.xml
index f3acf267..836f69b4 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.4.1
+ 1.4.2
\ No newline at end of file
diff --git a/app.json b/app.json
index 8593f64c..a73174b9 100644
--- a/app.json
+++ b/app.json
@@ -2,7 +2,7 @@
"expo": {
"name": "Nuvio",
"slug": "nuvio",
- "version": "1.4.1",
+ "version": "1.4.2",
"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": "37",
+ "buildNumber": "38",
"infoPlist": {
"NSAppTransportSecurity": {
"NSAllowsArbitraryLoads": true
@@ -60,7 +60,7 @@
"android.permission.WRITE_SETTINGS"
],
"package": "com.nuvio.app",
- "versionCode": 37,
+ "versionCode": 38,
"architectures": [
"arm64-v8a",
"armeabi-v7a",
@@ -113,6 +113,6 @@
"fallbackToCacheTimeout": 30000,
"url": "https://ota.nuvioapp.space/api/manifest"
},
- "runtimeVersion": "1.4.1"
+ "runtimeVersion": "1.4.2"
}
}
diff --git a/ios/Nuvio/Info.plist b/ios/Nuvio/Info.plist
index eaae9f2f..24d1caaa 100644
--- a/ios/Nuvio/Info.plist
+++ b/ios/Nuvio/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.4.1
+ 1.4.2
CFBundleSignature
????
CFBundleURLTypes
@@ -39,7 +39,7 @@
CFBundleVersion
- 37
+ 38
LSApplicationQueriesSchemes
vlc
diff --git a/src/contexts/DownloadsContext.tsx b/src/contexts/DownloadsContext.tsx
index 4f25e2a7..9a1b64b4 100644
--- a/src/contexts/DownloadsContext.tsx
+++ b/src/contexts/DownloadsContext.tsx
@@ -35,6 +35,7 @@ export interface DownloadItem {
sourceUrl: string; // stream url
headers?: Record;
fileUri?: string; // local file uri once downloading/finished
+ relativeFilePath?: string; // stable path under the app documents dir (survives sandbox path changes)
createdAt: number;
updatedAt: number;
// Additional metadata for progress tracking
@@ -231,6 +232,55 @@ function toFileUri(pathOrUri: string): string {
return pathOrUri;
}
+function normalizeRelativePath(path: string): string {
+ return path.replace(/\\/g, '/').replace(/^\/+/, '');
+}
+
+function getDocumentsDirPath(): string {
+ return stripFileScheme(String((directories as any).documents || (FileSystem as any).documentDirectory || ''));
+}
+
+function getRelativeDownloadPath(pathOrUri?: string | null): string | undefined {
+ if (!pathOrUri) return undefined;
+
+ const withoutScheme = stripFileScheme(String(pathOrUri)).replace(/\\/g, '/').trim();
+ if (!withoutScheme) return undefined;
+
+ const relativeCandidate = normalizeRelativePath(withoutScheme);
+ if (!withoutScheme.startsWith('/') && relativeCandidate.startsWith('downloads/')) {
+ return relativeCandidate;
+ }
+
+ const downloadsMatch = withoutScheme.match(/(?:^|\/)(downloads\/.+)$/);
+ if (downloadsMatch?.[1]) {
+ return normalizeRelativePath(downloadsMatch[1]);
+ }
+
+ const documentsDir = getDocumentsDirPath().replace(/\\/g, '/').replace(/\/+$/, '');
+ if (documentsDir && withoutScheme.startsWith(`${documentsDir}/`)) {
+ return normalizeRelativePath(withoutScheme.slice(documentsDir.length + 1));
+ }
+
+ if (!withoutScheme.startsWith('/') && !withoutScheme.includes('://')) {
+ return relativeCandidate;
+ }
+
+ return undefined;
+}
+
+function resolveDownloadFileUri(relativeFilePath?: string | null, fileUri?: string | null): string | undefined {
+ const relativePath = getRelativeDownloadPath(relativeFilePath) || getRelativeDownloadPath(fileUri);
+ if (relativePath) {
+ const documentsDir = getDocumentsDirPath();
+ if (documentsDir) {
+ return toFileUri(`${documentsDir}/${relativePath}`);
+ }
+ }
+
+ if (fileUri) return toFileUri(String(fileUri));
+ return undefined;
+}
+
export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [downloads, setDownloads] = useState([]);
const downloadsRef = useRef(downloads);
@@ -272,7 +322,8 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
posterUrl: (d.posterUrl as any) ?? null,
sourceUrl: String(d.sourceUrl ?? ''),
headers: (d.headers as any) ?? undefined,
- fileUri: d.fileUri ? String(d.fileUri) : undefined,
+ fileUri: resolveDownloadFileUri((d as any).relativeFilePath, d.fileUri),
+ relativeFilePath: getRelativeDownloadPath((d as any).relativeFilePath) || getRelativeDownloadPath(d.fileUri),
createdAt: typeof d.createdAt === 'number' ? d.createdAt : Date.now(),
updatedAt: typeof d.updatedAt === 'number' ? d.updatedAt : Date.now(),
// Restore metadata for progress tracking
@@ -463,6 +514,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
.done(({ location, bytesDownloaded, bytesTotal }: any) => {
const finalPath = location ? String(location) : '';
const finalUri = finalPath ? toFileUri(finalPath) : undefined;
+ const relativeFilePath = getRelativeDownloadPath(finalPath || finalUri);
updateDownload(taskId, (d) => ({
...d,
@@ -472,6 +524,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
progress: 100,
updatedAt: Date.now(),
fileUri: finalUri || d.fileUri,
+ relativeFilePath: relativeFilePath || d.relativeFilePath,
resumeData: undefined,
}));
@@ -539,7 +592,8 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
posterUrl: meta.posterUrl ?? null,
sourceUrl: String(meta.sourceUrl ?? ''),
headers: meta.headers,
- fileUri: meta.fileUri,
+ fileUri: resolveDownloadFileUri(meta.relativeFilePath, meta.fileUri),
+ relativeFilePath: getRelativeDownloadPath(meta.relativeFilePath) || getRelativeDownloadPath(meta.fileUri),
createdAt,
updatedAt: createdAt,
imdbId: meta.imdbId,
@@ -564,11 +618,12 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
const list = downloadsRef.current;
await Promise.all(
list.map(async (d) => {
- if (!d.fileUri) return;
+ const resolvedFileUri = resolveDownloadFileUri(d.relativeFilePath, d.fileUri);
+ if (!resolvedFileUri) return;
if (d.status === 'completed' || d.status === 'queued') return;
try {
- const info = await FileSystem.getInfoAsync(d.fileUri);
+ const info = await FileSystem.getInfoAsync(resolvedFileUri);
if (!info.exists || typeof info.size !== 'number') return;
let totalBytes = d.totalBytes;
@@ -588,6 +643,8 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
totalBytes: totalBytes || prev.totalBytes,
progress: looksComplete ? 100 : Math.min(99, Math.max(prev.progress, progress)),
status: looksComplete ? 'completed' : prev.status,
+ fileUri: resolvedFileUri,
+ relativeFilePath: prev.relativeFilePath || getRelativeDownloadPath(resolvedFileUri),
resumeData: looksComplete ? undefined : prev.resumeData,
updatedAt: Date.now(),
}));
@@ -595,7 +652,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
if (looksComplete) {
const done = downloadsRef.current.find(x => x.id === d.id);
if (done) {
- notifyCompleted({ ...done, status: 'completed', progress: 100, fileUri: d.fileUri } as DownloadItem);
+ notifyCompleted({ ...done, status: 'completed', progress: 100, fileUri: resolvedFileUri } as DownloadItem);
stopLiveActivityForDownload(d.id, { title: done.title, subtitle: 'Completed', progressPercent: 100 });
} else {
stopLiveActivityForDownload(d.id, { subtitle: 'Completed', progressPercent: 100 });
@@ -693,7 +750,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
}
}
- const documentsDir = stripFileScheme(String((directories as any).documents || ''));
+ const documentsDir = getDocumentsDirPath();
if (!documentsDir) throw new Error('Downloads directory is not available');
const uniqueId = `${Date.now()}_${Math.random().toString(36).substring(7)}`;
@@ -713,6 +770,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
} catch { }
const fileUri = toFileUri(destinationPath);
+ const relativeFilePath = getRelativeDownloadPath(destinationPath);
const createdAt = Date.now();
const newItem: DownloadItem = {
@@ -737,6 +795,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
sourceUrl: input.url,
headers: input.headers,
fileUri,
+ relativeFilePath,
createdAt,
updatedAt: createdAt,
// Store metadata for progress tracking
@@ -770,6 +829,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
sourceUrl: input.url,
headers: input.headers,
fileUri,
+ relativeFilePath,
imdbId: input.imdbId,
tmdbId: input.tmdbId,
},
@@ -823,8 +883,9 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
}
const item = downloadsRef.current.find(d => d.id === id);
- if (item?.fileUri) {
- await FileSystem.deleteAsync(item.fileUri, { idempotent: true }).catch(() => { });
+ const resolvedFileUri = resolveDownloadFileUri(item?.relativeFilePath, item?.fileUri);
+ if (resolvedFileUri) {
+ await FileSystem.deleteAsync(resolvedFileUri, { idempotent: true }).catch(() => { });
}
setDownloads(prev => prev.filter(d => d.id !== id));
}, [stopLiveActivityForDownload]);
@@ -832,8 +893,9 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
const removeDownload = useCallback(async (id: string) => {
const item = downloadsRef.current.find(d => d.id === id);
await stopLiveActivityForDownload(id, { title: item?.title, subtitle: 'Removed', progressPercent: item?.progress });
- if (item?.fileUri && item.status === 'completed') {
- await FileSystem.deleteAsync(item.fileUri, { idempotent: true }).catch(() => { });
+ const resolvedFileUri = resolveDownloadFileUri(item?.relativeFilePath, item?.fileUri);
+ if (resolvedFileUri && item?.status === 'completed') {
+ await FileSystem.deleteAsync(resolvedFileUri, { idempotent: true }).catch(() => { });
}
setDownloads(prev => prev.filter(d => d.id !== id));
}, [stopLiveActivityForDownload]);
@@ -863,4 +925,3 @@ export function useDownloads(): DownloadsContextValue {
return ctx;
}
-
diff --git a/src/utils/version.ts b/src/utils/version.ts
index 96fe25be..ab5af826 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.4.1';
+export const APP_VERSION = '1.4.2';
export function getDisplayedAppVersion(): string {
return APP_VERSION;