mirror of
https://github.com/ImranR98/Obtainium.git
synced 2026-07-31 01:17:08 -04:00
App sources: - Harden sources against malformed/changed responses (null, parse, index and empty-version guards): rustore, tencent, apkmirror, gitlab, aptoide, fdroid(repo), sourcehut, huaweiappgallery, itchio, izzyondroid, telegramapp, uptodown, vivoappstore, etc. - APKCombo: fix and enable (issue #341) - unwrap "/r2?u=" links to the signed Cloudflare R2 URL, drop the Host header that 403'd the R2 download, take one build per variant, and parse the release date; register the source. - Remove orphaned Mullvad source (never registered). - F-Droid version-regex filter selects the newest match; fix Jenkins changelog link; GitLab reuses one GitHub helper instance; GitHubStars reuses one GitHub instance. UI / behaviour: - Fix download progress bar never updating on the app detail page and the apps list (provider select on an in-place-mutated AppInMemory never fired a rebuild). - Fix single-category gradient crash, dead "installing" tile state, silent swipe-install errors, deep-link dedup, mounted/context guards in import/export and add-app, and ExportSection future caching. - generated_form: GeneratedFormSwitch.disabled is now applied; clone() preserves disabled and autoCompleteOptions. Performance: - Cache SettingsProvider.categories (was rebuilding a Map + re-parsing JSON on every access, over-rebuilding category selectors). - Memoize the flattened combined form items used by the per-app JSON migration at load. Tooling / config: - build.sh: set -euo pipefail; make the --build-id=none workaround idempotent and glob-safe. - pubspec: branch-track git dependencies. - Remove unused config_keys.dart (consolidate on raw setting keys).
185 lines
5.5 KiB
Dart
185 lines
5.5 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:android_package_installer/android_package_installer.dart';
|
|
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:obtainium/providers/logs_provider.dart';
|
|
import 'package:obtainium/providers/source_provider.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
class ObtainiumError {
|
|
late String message;
|
|
bool unexpected;
|
|
ObtainiumError(this.message, {this.unexpected = false});
|
|
@override
|
|
String toString() {
|
|
return message;
|
|
}
|
|
}
|
|
|
|
class RateLimitError extends ObtainiumError {
|
|
late int remainingMinutes;
|
|
RateLimitError(this.remainingMinutes)
|
|
: super(plural('tooManyRequestsTryAgainInMinutes', remainingMinutes));
|
|
}
|
|
|
|
class InvalidURLError extends ObtainiumError {
|
|
InvalidURLError(String sourceName)
|
|
: super(tr('invalidURLForSource', args: [sourceName]));
|
|
}
|
|
|
|
class CredsNeededError extends ObtainiumError {
|
|
CredsNeededError(String sourceName)
|
|
: super(tr('requiresCredentialsInSettings', args: [sourceName]));
|
|
}
|
|
|
|
class NoReleasesError extends ObtainiumError {
|
|
NoReleasesError({String? note})
|
|
: super(
|
|
'${tr('noReleaseFound')}${note?.isNotEmpty == true ? '\n\n$note' : ''}',
|
|
);
|
|
}
|
|
|
|
class NoAPKError extends ObtainiumError {
|
|
NoAPKError() : super(tr('noAPKFound'));
|
|
}
|
|
|
|
class NoVersionError extends ObtainiumError {
|
|
NoVersionError() : super(tr('noVersionFound'));
|
|
}
|
|
|
|
class UnsupportedURLError extends ObtainiumError {
|
|
UnsupportedURLError() : super(tr('urlMatchesNoSource'));
|
|
}
|
|
|
|
class DowngradeError extends ObtainiumError {
|
|
DowngradeError(int currentVersionCode, int newVersionCode)
|
|
: super(
|
|
'${tr('cantInstallOlderVersion')} (versionCode $currentVersionCode ➔ $newVersionCode)',
|
|
);
|
|
}
|
|
|
|
class InstallError extends ObtainiumError {
|
|
InstallError(int code)
|
|
: super(PackageInstallerStatus.byCode(code).name.substring(7));
|
|
}
|
|
|
|
class IDChangedError extends ObtainiumError {
|
|
IDChangedError(String newId) : super('${tr('appIdMismatch')} - $newId');
|
|
}
|
|
|
|
class RepositoryRenamedError extends ObtainiumError {
|
|
final String oldUrl;
|
|
final String newUrl;
|
|
RepositoryRenamedError(this.oldUrl, this.newUrl) : super(tr('repoRenamed'));
|
|
}
|
|
|
|
class NotImplementedError extends ObtainiumError {
|
|
NotImplementedError() : super(tr('functionNotImplemented'));
|
|
}
|
|
|
|
class MultiAppMultiError extends ObtainiumError {
|
|
Map<String, dynamic> rawErrors = {};
|
|
Map<String, List<String>> idsByErrorString = {};
|
|
Map<String, String> appIdNames = {};
|
|
|
|
MultiAppMultiError() : super(tr('placeholder'), unexpected: true);
|
|
|
|
void add(String appId, dynamic error, {String? appName}) {
|
|
if (error is SocketException) {
|
|
error = error.message;
|
|
}
|
|
rawErrors[appId] = error;
|
|
var string = error.toString();
|
|
var tempIds = idsByErrorString.remove(string);
|
|
tempIds ??= [];
|
|
tempIds.add(appId);
|
|
idsByErrorString.putIfAbsent(string, () => tempIds!);
|
|
if (appName != null) {
|
|
appIdNames[appId] = appName;
|
|
}
|
|
}
|
|
|
|
String errorString(String appId, {bool includeIdsWithNames = false}) =>
|
|
'${appIdNames.containsKey(appId) ? '${appIdNames[appId]}${includeIdsWithNames ? ' ($appId)' : ''}' : appId}: ${rawErrors[appId].toString()}';
|
|
|
|
String errorsAppsString(
|
|
String errString,
|
|
List<String> appIds, {
|
|
bool includeIdsWithNames = false,
|
|
}) =>
|
|
'$errString [${list2FriendlyString(appIds.map((id) => appIdNames.containsKey(id) == true ? '${appIdNames[id]}${includeIdsWithNames ? ' ($id)' : ''}' : id).toList())}]';
|
|
|
|
@override
|
|
String toString() => idsByErrorString.entries
|
|
.map((e) => errorsAppsString(e.key, e.value))
|
|
.join('\n\n');
|
|
}
|
|
|
|
void showMessage(dynamic e, BuildContext context, {bool isError = false}) {
|
|
Provider.of<LogsProvider>(
|
|
context,
|
|
listen: false,
|
|
).add(e.toString(), level: isError ? LogLevels.error : LogLevels.info);
|
|
if (e is String || (e is ObtainiumError && !e.unexpected)) {
|
|
ScaffoldMessenger.of(
|
|
context,
|
|
).showSnackBar(SnackBar(content: Text(e.toString())));
|
|
} else {
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext ctx) {
|
|
return AlertDialog(
|
|
scrollable: true,
|
|
title: Text(
|
|
e is MultiAppMultiError
|
|
? tr(isError ? 'someErrors' : 'updates')
|
|
: tr(isError ? 'unexpectedError' : 'unknown'),
|
|
),
|
|
content: GestureDetector(
|
|
onLongPress: () {
|
|
Clipboard.setData(ClipboardData(text: e.toString()));
|
|
ScaffoldMessenger.of(
|
|
context,
|
|
).showSnackBar(SnackBar(content: Text(tr('copiedToClipboard'))));
|
|
},
|
|
child: Text(e.toString()),
|
|
),
|
|
actions: [
|
|
FilledButton.tonal(
|
|
onPressed: () {
|
|
Navigator.of(context).pop(null);
|
|
},
|
|
child: Text(tr('ok')),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
void showError(dynamic e, BuildContext context) {
|
|
showMessage(e, context, isError: true);
|
|
}
|
|
|
|
String list2FriendlyString(List<String> list) {
|
|
var isUsingEnglish = isEnglish();
|
|
return list.length == 2
|
|
? '${list[0]} ${tr('and')} ${list[1]}'
|
|
: list
|
|
.asMap()
|
|
.entries
|
|
.map(
|
|
(e) =>
|
|
e.value +
|
|
(e.key == list.length - 1
|
|
? ''
|
|
: e.key == list.length - 2
|
|
? '${isUsingEnglish ? ',' : ''} ${tr('and')} '
|
|
: ', '),
|
|
)
|
|
.join('');
|
|
}
|