diff --git a/.flutter b/.flutter index 68415ad1..c519ee91 160000 --- a/.flutter +++ b/.flutter @@ -1 +1 @@ -Subproject commit 68415ad1d920f6fe5ec284f5c2febf7c4dd5b0b3 +Subproject commit c519ee916eaeb88923e67befb89c0f1dabfa83e6 diff --git a/assets/screenshots/1.apps.png b/assets/screenshots/1.apps.png index ecd7aa82..ba9ec463 100644 Binary files a/assets/screenshots/1.apps.png and b/assets/screenshots/1.apps.png differ diff --git a/assets/screenshots/2.dark_theme.png b/assets/screenshots/2.dark_theme.png index 699bbf7a..77b3d96c 100644 Binary files a/assets/screenshots/2.dark_theme.png and b/assets/screenshots/2.dark_theme.png differ diff --git a/assets/screenshots/3.material_you.png b/assets/screenshots/3.material_you.png index 04b0ae16..63abd7d3 100644 Binary files a/assets/screenshots/3.material_you.png and b/assets/screenshots/3.material_you.png differ diff --git a/assets/screenshots/4.app.png b/assets/screenshots/4.app.png index 55ae937f..f26ab8a0 100644 Binary files a/assets/screenshots/4.app.png and b/assets/screenshots/4.app.png differ diff --git a/assets/screenshots/5.app_opts.png b/assets/screenshots/5.app_opts.png index 5e5e7fe4..8a3bd88d 100644 Binary files a/assets/screenshots/5.app_opts.png and b/assets/screenshots/5.app_opts.png differ diff --git a/assets/screenshots/6.app_webview.png b/assets/screenshots/6.app_webview.png index 636eaf28..10c8f5e7 100644 Binary files a/assets/screenshots/6.app_webview.png and b/assets/screenshots/6.app_webview.png differ diff --git a/lib/app_sources/github.dart b/lib/app_sources/github.dart index 810ef91b..ab246e69 100644 --- a/lib/app_sources/github.dart +++ b/lib/app_sources/github.dart @@ -501,7 +501,7 @@ class GitHub extends AppSource { AppNames getAppNames(String standardUrl) { String temp = standardUrl.substring(standardUrl.indexOf('://') + 3); List names = temp.substring(temp.indexOf('/') + 1).split('/'); - return AppNames(names[0], names[1]); + return AppNames(names[0], names.sublist(1).join('/')); } Future>> searchCommon( diff --git a/lib/app_sources/gitlab.dart b/lib/app_sources/gitlab.dart index e8404a43..b47e8914 100644 --- a/lib/app_sources/gitlab.dart +++ b/lib/app_sources/gitlab.dart @@ -54,7 +54,7 @@ class GitLab extends AppSource { @override String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) { RegExp standardUrlRegEx = RegExp( - '^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+', + '^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+(/[^/]+){1,20}', caseSensitive: false); RegExpMatch? match = standardUrlRegEx.firstMatch(url); if (match == null) { @@ -126,6 +126,8 @@ class GitLab extends AppSource { ) async { // Prepare request params var names = GitHub().getAppNames(standardUrl); + String projectUriComponent = + '${Uri.encodeComponent(names.author)}%2F${Uri.encodeComponent(names.name)}'; String? PAT = await getPATIfAny(hostChanged ? additionalSettings : {}); String optionalAuth = (PAT != null) ? 'private_token=$PAT' : ''; @@ -133,7 +135,7 @@ class GitLab extends AppSource { // Get project ID Response res0 = await sourceRequest( - 'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}?$optionalAuth', + 'https://${hosts[0]}/api/v4/projects/$projectUriComponent?$optionalAuth', additionalSettings); if (res0.statusCode != 200) { throw getObtainiumHttpError(res0); @@ -145,7 +147,7 @@ class GitLab extends AppSource { // Request data from REST API Response res = await sourceRequest( - 'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/${trackOnly ? 'repository/tags' : 'releases'}?$optionalAuth', + 'https://${hosts[0]}/api/v4/projects/$projectUriComponent/${trackOnly ? 'repository/tags' : 'releases'}?$optionalAuth', additionalSettings); if (res.statusCode != 200) { throw getObtainiumHttpError(res); @@ -180,7 +182,7 @@ class GitLab extends AppSource { return APKDetails( e['tag_name'] ?? e['name'], getApkUrlsFromUrls(apkUrlsSet.toList()), - GitHub().getAppNames(standardUrl), + AppNames(names.author, names.name.split('/').last), releaseDate: releaseDate); }); if (apkDetailsList.isEmpty) { diff --git a/lib/main.dart b/lib/main.dart index 50ef0f2f..db1c0be1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -44,7 +44,8 @@ List> supportedLocales = const [ MapEntry(Locale('da'), 'Dansk'), MapEntry(Locale('en', 'EO'), 'Esperanto'), // https://github.com/aissat/easy_localization/issues/220#issuecomment-846035493 - MapEntry(Locale('in'), 'Bahasa Indonesia') + MapEntry(Locale('in'), 'Bahasa Indonesia'), + MapEntry(Locale('ko'), '한국어'), ]; const fallbackLocale = Locale('en'); const localeDir = 'assets/translations'; @@ -244,6 +245,7 @@ class _ObtainiumState extends State { supportedLocales: context.supportedLocales, locale: context.locale, navigatorKey: globalNavigatorKey, + debugShowCheckedModeBanner: false, theme: ThemeData( useMaterial3: true, colorScheme: settingsProvider.theme == ThemeSettings.dark diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index a28a8a93..b787ab42 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -151,13 +151,15 @@ Future downloadFileWithRetry(String url, String fileName, {bool useExisting = true, Map? headers, int retries = 3, - bool allowInsecure = false}) async { + bool allowInsecure = false, + LogsProvider? logs}) async { try { return await downloadFile( url, fileName, fileNameHasExt, onProgress, destDir, useExisting: useExisting, headers: headers, - allowInsecure: allowInsecure); + allowInsecure: allowInsecure, + logs: logs); } catch (e) { if (retries > 0 && e is ClientException) { await Future.delayed(const Duration(seconds: 5)); @@ -166,7 +168,8 @@ Future downloadFileWithRetry(String url, String fileName, useExisting: useExisting, headers: headers, retries: (retries - 1), - allowInsecure: allowInsecure); + allowInsecure: allowInsecure, + logs: logs); } else { rethrow; } @@ -219,7 +222,8 @@ Future downloadFile(String url, String fileName, bool fileNameHasExt, Function? onProgress, String destDir, {bool useExisting = true, Map? headers, - bool allowInsecure = false}) async { + bool allowInsecure = false, + LogsProvider? logs}) async { // Send the initial request but cancel it as soon as you have the headers var reqHeaders = headers ?? {}; var req = Request('GET', Uri.parse(url)); @@ -280,6 +284,42 @@ Future downloadFile(String url, String fileName, bool fileNameHasExt, // Download to a '.temp' file (to distinguish btn. complete/incomplete files) File tempDownloadedFile = File('${downloadedFile.path}.part'); + // If there is already a temp file, a download may already be in progress - account for this (see #2073) + bool tempFileExists = tempDownloadedFile.existsSync(); + if (tempFileExists && useExisting) { + logs?.add( + 'Partial download exists - will wait: ${tempDownloadedFile.uri.pathSegments.last}'); + bool isDownloading = true; + int currentTempFileSize = await tempDownloadedFile.length(); + bool shouldReturn = false; + while (isDownloading) { + await Future.delayed(Duration(seconds: 7)); + if (tempDownloadedFile.existsSync()) { + int newTempFileSize = await tempDownloadedFile.length(); + if (newTempFileSize > currentTempFileSize) { + currentTempFileSize = newTempFileSize; + logs?.add( + 'Existing partial download still in progress: ${tempDownloadedFile.uri.pathSegments.last}'); + } else { + logs?.add( + 'Ignoring existing partial download: ${tempDownloadedFile.uri.pathSegments.last}'); + break; + } + } else { + shouldReturn = downloadedFile.existsSync(); + } + } + if (shouldReturn) { + logs?.add( + 'Existing partial download completed - not repeating: ${tempDownloadedFile.uri.pathSegments.last}'); + client.close(); + return downloadedFile; + } else { + logs?.add( + 'Existing partial download not in progress: ${tempDownloadedFile.uri.pathSegments.last}'); + } + } + // If the range feature is not available (or you need to start a ranged req from 0), // complete the already-started request, else cancel it and start a ranged request, // and open the file for writing in the appropriate mode @@ -419,9 +459,7 @@ class AppsProvider with ChangeNotifier { // Delete any partial APKs (if safe to do so) var cutoff = DateTime.now().subtract(const Duration(days: 7)); APKDir.listSync() - .where((element) => - element.path.endsWith('.part') || - element.statSync().modified.isBefore(cutoff)) + .where((element) => element.statSync().modified.isBefore(cutoff)) .forEach((partialApk) { if (!areDownloadsRunning()) { partialApk.delete(recursive: true); @@ -495,7 +533,8 @@ class AppsProvider with ChangeNotifier { prevProg = prog; }, APKDir.path, useExisting: useExisting, - allowInsecure: app.additionalSettings['allowInsecure'] == true); + allowInsecure: app.additionalSettings['allowInsecure'] == true, + logs: logs); // Set to 90 for remaining steps, will make null in 'finally' if (apps[app.id] != null) { apps[app.id]!.downloadProgress = -1; @@ -1124,7 +1163,8 @@ class AppsProvider with ChangeNotifier { forAPKDownload: fileUrl.key.endsWith('.apk') ? true : false), useExisting: false, - allowInsecure: app.additionalSettings['allowInsecure'] == true); + allowInsecure: app.additionalSettings['allowInsecure'] == true, + logs: logs); notificationsProvider .notify(DownloadedNotification(fileUrl.key, fileUrl.value)); } catch (e) { @@ -1414,8 +1454,10 @@ class AppsProvider with ChangeNotifier { app = getCorrectedInstallStatusAppIfPossible(app, info) ?? app; } if (!onlyIfExists || this.apps.containsKey(app.id)) { - File('${(await getAppsDir()).path}/${app.id}.json') - .writeAsStringSync(jsonEncode(app.toJson())); + String filePath = '${(await getAppsDir()).path}/${app.id}.json'; + File('$filePath.tmp') + .writeAsStringSync(jsonEncode(app.toJson())); // #2089 + File('$filePath.tmp').renameSync(filePath); } try { this.apps.update(app.id, diff --git a/pubspec.lock b/pubspec.lock index 1e4b6471..b6a9e570 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -224,10 +224,10 @@ packages: dependency: transitive description: name: dbus - sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" device_info_plus: dependency: "direct main" description: @@ -256,10 +256,10 @@ packages: dependency: "direct main" description: name: easy_localization - sha256: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201 + sha256: "0f5239c7b8ab06c66440cfb0e9aa4b4640429c6668d5a42fe389c5de42220b12" url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "3.0.7+1" easy_logger: dependency: transitive description: @@ -405,10 +405,10 @@ packages: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: "31cd0885738e87c72d6f055564d37fabcdacee743b396b78c7636c169cac64f5" + sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c url: "https://pub.dev" source: hosted - version: "0.14.2" + version: "0.14.3" flutter_lints: dependency: "direct dev" description: @@ -524,10 +524,10 @@ packages: dependency: "direct main" description: name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.3.0" http_parser: dependency: transitive description: @@ -844,18 +844,18 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a + sha256: c59819dacc6669a1165d54d2735a9543f136f9b3cec94ca65cea6ab8dffc422e url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.4.0" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: bf808be89fe9dc467475e982c1db6c2faf3d2acf54d526cd5ec37d86c99dbd84 + sha256: "650584dcc0a39856f369782874e562efd002a9c94aec032412c9eb81419cce1f" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.4" shared_preferences_foundation: dependency: transitive description: @@ -1155,10 +1155,10 @@ packages: dependency: transitive description: name: webview_flutter_android - sha256: d1ee28f44894cbabb1d94cc42f9980297f689ff844d067ec50ff88d86e27d63f + sha256: "5568f17a9c25c0fdd0737900fa1c2d1fee2d780bc212d9aec10c2d1f48ef0f59" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.3.1" webview_flutter_platform_interface: dependency: transitive description: @@ -1171,18 +1171,18 @@ packages: dependency: transitive description: name: webview_flutter_wkwebview - sha256: "4adc14ea9a770cc9e2c8f1ac734536bd40e82615bd0fa6b94be10982de656cc7" + sha256: "8e0593559bfecd35eb1757d6907ed6b995a41ef82607d6113df897c2805ce6be" url: "https://pub.dev" source: hosted - version: "3.17.0" + version: "3.18.0" win32: dependency: transitive description: name: win32 - sha256: "154360849a56b7b67331c21f09a386562d88903f90a1099c5987afc1912e1f29" + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e url: "https://pub.dev" source: hosted - version: "5.10.0" + version: "5.10.1" win32_registry: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 97de4534..8c57967c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.1.39+2296 +version: 1.1.40+2297 environment: sdk: ^3.6.0