Compare commits

...

17 Commits

Author SHA1 Message Date
b165400a6e Merge pull request #229 from ImranR98/dev
Increment version
2023-01-15 11:45:05 -05:00
c47bf937f1 Increment version 2023-01-15 11:44:45 -05:00
2e19a8c04c Merge pull request #228 from gidano/main
Update hu.json
2023-01-15 11:42:28 -05:00
05d4da86ec Update hu.json 2023-01-15 17:39:57 +01:00
e9d1b04d54 Merge pull request #227 from ImranR98/dev
Increment version, upgrade packages
2023-01-15 11:20:45 -05:00
cff5334c25 Increment version, upgrade packages 2023-01-15 11:20:30 -05:00
a55346fc22 Merge pull request #226 from bluefly000/japanese-translation
Update Japanese translation
2023-01-15 11:19:01 -05:00
885df678e5 Update Japanese translation 2023-01-13 13:34:57 +09:00
bf7b0c5702 Merge pull request #225 from ImranR98/dev
2 New Sources: Codeberg and HTML Fallback
2023-01-12 22:33:50 -05:00
2972da4609 Upgraded packages 2023-01-12 22:28:47 -05:00
b8567af98e Increment version 2023-01-12 22:24:52 -05:00
ea62c68b40 Added the HTML fallback Source 2023-01-12 22:23:53 -05:00
08a5af0449 Added Codeberg as a Source + search UI bugfix 2023-01-12 20:57:53 -05:00
36f327c16e Merge pull request #220 from ImranR98/dev
- Obtainium would skip installing APKs that had the same [`versionCode`](https://developer.android.com/studio/publish/versioning#versioningsettings) since this number should be different for each new build of an App.
    - However, there are enough Apps that don't do this (#149, #219) so Obtainium now installs updates even if the `versionCode` has not changed.
- The GitHub release title filter has also been improved so that it filters by `tag_name` instead of `title` for releases with empty titles (as it seems like GitHub automatically displays the tag as the title in such cases).
2023-01-07 16:58:58 -05:00
768213cb34 Increment version 2023-01-07 16:50:01 -05:00
e888fb7120 Don't skip installing same-versionCode updates 2023-01-07 16:49:38 -05:00
1fb68dd674 GitHub release filter bugfix 2023-01-07 16:18:26 -05:00
12 changed files with 269 additions and 52 deletions

View File

@ -9,6 +9,7 @@ Motivation: [Side Of Burritos - You should use this instead of F-Droid | How to
Currently supported App sources:
- [GitHub](https://github.com/)
- [GitLab](https://gitlab.com/)
- [Codeberg](https://codeberg.org/)
- [F-Droid](https://f-droid.org/)
- [IzzyOnDroid](https://android.izzysoft.de/)
- [Mullvad](https://mullvad.net/en/)
@ -18,6 +19,8 @@ Currently supported App sources:
- Third Party F-Droid Repos
- Any URLs ending with `/fdroid/<word>`, where `<word>` can be anything - most often `repo`
- [Steam](https://store.steampowered.com/mobile)
- "HTML" (Fallback)
- Any other URL that returns an HTML page with links to APK files (if multiple, the last file alphabetically is picked)
## Limitations
- App installs happen asynchronously and the success/failure of an install cannot be determined directly. This results in install statuses and versions sometimes being out of sync with the OS until the next launch or until the problem is manually corrected.

View File

@ -207,9 +207,9 @@
"categoryDeleteWarning": "A(z) {} összes app kategorizálatlan állapotba kerül.",
"addCategory": "Új kategória",
"label": "Címke",
"language": "Language",
"storagePermissionDenied": "Storage permission denied",
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
"language": "Nyelv",
"storagePermissionDenied": "Tárhely engedély megtagadva",
"selectedCategorizeWarning": "Ez felváltja a kiválasztott alkalmazások meglévő kategória-beállításait.",
"tooManyRequestsTryAgainInMinutes": {
"one": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva",
"other": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva"

View File

@ -12,7 +12,7 @@
"ok": "OK",
"and": "と",
"startedBgUpdateTask": "バックグラウンドのアップデート確認タスクを開始",
"bgUpdateIgnoreAfterIs": "Bg update ignoreAfter is {}",
"bgUpdateIgnoreAfterIs": "Bg update ignoreAfter is {}",
"startedActualBGUpdateCheck": "実際のバックグラウンドのアップデート確認を開始",
"bgUpdateTaskFinished": "バックグラウンドのアップデート確認タスクを終了",
"firstRun": "これがObtainiumの最初の実行です",
@ -65,13 +65,13 @@
"estimateInBrackets": "(推定)",
"selectAll": "すべて選択",
"deselectN": "{}件の選択を解除",
"xWillBeRemovedButRemainInstalled": "{}はObtainiumから削除されますが、デバイスにはインストールされたままです。",
"xWillBeRemovedButRemainInstalled": "{} はObtainiumから削除されますが、デバイスにはインストールされたままです。",
"removeSelectedAppsQuestion": "選択したアプリを削除しますか?",
"removeSelectedApps": "選択したアプリを削除する",
"updateX": "{}をアップデートする",
"installX": "{}をインストールする",
"updateX": "{} をアップデートする",
"installX": "{} をインストールする",
"markXTrackOnlyAsUpdated": "{}\n(追跡のみ)\nをアップデート済みとしてマークする",
"changeX": "{}を変更する",
"changeX": "{} を変更する",
"installUpdateApps": "アプリのインストール/アップデート",
"installUpdateSelectedApps": "選択したアプリのインストール/アップデート",
"onlyWorksWithNonEVDApps": "インストール状況を自動検出できないアプリ(一般的でないもの)のみ動作します。",
@ -145,23 +145,23 @@
"appNotFound": "アプリが見つかりません",
"obtainiumExportHyphenatedLowercase": "obtainium-エクスポート",
"pickAnAPK": "APKを選択",
"appHasMoreThanOnePackage": "{}は複数のパッケージが存在します: ",
"deviceSupportsXArch": "お使いのデバイスは{} CPUアーキテクチャに対応しています。",
"appHasMoreThanOnePackage": "{} は複数のパッケージが存在します: ",
"deviceSupportsXArch": "お使いのデバイスは {} CPUアーキテクチャに対応しています。",
"deviceSupportsFollowingArchs": "お使いのデバイスは、以下のCPUアーキテクチャをサポートしています:",
"warning": "警告",
"sourceIsXButPackageFromYPrompt": "アプリのソースは'{}'ですが、リリースパッケージは'{}'から来ています。続行しますか?",
"updatesAvailable": "アップデートが利用可能",
"updatesAvailableNotifDescription": "Obtainiumが追跡している1つまたは複数のアプリのアップデートが利用可能であることをユーザーに通知する",
"noNewUpdates": "新しいアップデートはありません",
"xHasAnUpdate": "{}のアップデートが利用可能です",
"xHasAnUpdate": "{} のアップデートが利用可能です",
"appsUpdated": "アプリをアップデートしました",
"appsUpdatedNotifDescription": "1つまたは複数のAppのアップデートがバックグラウンドで適用されたことをユーザーに通知する",
"xWasUpdatedToY": "{}{}にアップデートされました",
"xWasUpdatedToY": "{}{} にアップデートされました",
"errorCheckingUpdates": "アップデート確認中のエラー",
"errorCheckingUpdatesNotifDescription": "バックグラウンドでのアップデート確認に失敗した際に表示される通知",
"appsRemoved": "削除されたアプリ",
"appsRemovedNotifDescription": "アプリの読み込み中にエラーが発生したため、1つまたは複数のアプリが削除されたことをユーザーに通知する",
"xWasRemovedDueToErrorY": "このエラーのため、{}は削除されました: {}",
"xWasRemovedDueToErrorY": "このエラーのため、{} は削除されました: {}",
"completeAppInstallation": "アプリのインストールを完了する",
"obtainiumMustBeOpenToInstallApps": "アプリをインストールするにはObtainiumを開いている必要があります。",
"completeAppInstallationNotifDescription": "アプリのインストールを完了するために、Obtainiumに戻る必要があります。",
@ -209,8 +209,8 @@
"addCategory": "カテゴリを追加",
"label": "ラベル",
"language": "言語",
"storagePermissionDenied": "Storage permission denied",
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
"storagePermissionDenied": "ストレージ権限が拒否されました",
"selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。",
"tooManyRequestsTryAgainInMinutes": {
"one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください",
"other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください"
@ -248,11 +248,11 @@
"other": "{n}個のログをクリアしました (前 = {before}, 後 = {after})"
},
"xAndNMoreUpdatesAvailable": {
"one": "{}とさらに{}個のアプリのアップデートが利用可能です",
"other": "{}とさらに{}個のアプリのアップデートが利用可能です"
"one": "{} とさらに {} 個のアプリのアップデートが利用可能です",
"other": "{} とさらに {} 個のアプリのアップデートが利用可能です"
},
"xAndNMoreUpdatesInstalled": {
"one": "{}とさらに{}個のアプリがアップデートされました",
"other": "{}とさらに{}個のアプリがアップデートされました"
"one": "{} とさらに {} 個のアプリがアップデートされました",
"other": "{} とさらに {} 個のアプリがアップデートされました"
}
}

View File

@ -0,0 +1,157 @@
import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
class Codeberg extends AppSource {
Codeberg() {
host = 'codeberg.org';
additionalSourceSpecificSettingFormItems = [];
additionalSourceAppSpecificSettingFormItems = [
[
GeneratedFormSwitch('includePrereleases',
label: tr('includePrereleases'), defaultValue: false)
],
[
GeneratedFormSwitch('fallbackToOlderReleases',
label: tr('fallbackToOlderReleases'), defaultValue: true)
],
[
GeneratedFormTextField('filterReleaseTitlesByRegEx',
label: tr('filterReleaseTitlesByRegEx'),
required: false,
additionalValidators: [
(value) {
if (value == null || value.isEmpty) {
return null;
}
try {
RegExp(value);
} catch (e) {
return tr('invalidRegEx');
}
return null;
}
])
]
];
canSearch = true;
}
@override
String standardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
}
@override
String? changeLogPageFromStandardUrl(String standardUrl) =>
'$standardUrl/releases';
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
bool includePrereleases = additionalSettings['includePrereleases'];
bool fallbackToOlderReleases =
additionalSettings['fallbackToOlderReleases'];
String? regexFilter =
(additionalSettings['filterReleaseTitlesByRegEx'] as String?)
?.isNotEmpty ==
true
? additionalSettings['filterReleaseTitlesByRegEx']
: null;
Response res = await get(Uri.parse(
'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/releases'));
if (res.statusCode == 200) {
var releases = jsonDecode(res.body) as List<dynamic>;
List<String> getReleaseAPKUrls(dynamic release) =>
(release['assets'] as List<dynamic>?)
?.map((e) {
return e['name'] != null && e['browser_download_url'] != null
? MapEntry(e['name'] as String,
e['browser_download_url'] as String)
: const MapEntry('', '');
})
.where((element) => element.key.toLowerCase().endsWith('.apk'))
.map((e) => e.value)
.toList() ??
[];
dynamic targetRelease;
for (int i = 0; i < releases.length; i++) {
if (!fallbackToOlderReleases && i > 0) break;
if (!includePrereleases && releases[i]['prerelease'] == true) {
continue;
}
if (releases[i]['draft'] == true) {
// Draft releases not supported
}
var nameToFilter = releases[i]['name'] as String;
if (nameToFilter.trim().isEmpty) {
// Some leave titles empty so tag is used
nameToFilter = releases[i]['tag_name'] as String;
}
if (regexFilter != null &&
!RegExp(regexFilter).hasMatch(nameToFilter.trim())) {
continue;
}
var apkUrls = getReleaseAPKUrls(releases[i]);
if (apkUrls.isEmpty && additionalSettings['trackOnly'] != true) {
continue;
}
targetRelease = releases[i];
targetRelease['apkUrls'] = apkUrls;
break;
}
if (targetRelease == null) {
throw NoReleasesError();
}
String? version = targetRelease['tag_name'];
if (version == null) {
throw NoVersionError();
}
return APKDetails(version, targetRelease['apkUrls'] as List<String>,
getAppNames(standardUrl));
} else {
throw getObtainiumHttpError(res);
}
}
AppNames getAppNames(String standardUrl) {
String temp = standardUrl.substring(standardUrl.indexOf('://') + 3);
List<String> names = temp.substring(temp.indexOf('/') + 1).split('/');
return AppNames(names[0], names[1]);
}
@override
Future<Map<String, String>> search(String query) async {
Response res = await get(Uri.parse(
'https://$host/api/v1/repos/search?q=${Uri.encodeQueryComponent(query)}&limit=100'));
if (res.statusCode == 200) {
Map<String, String> urlsWithDescriptions = {};
for (var e in (jsonDecode(res.body)['data'] as List<dynamic>)) {
urlsWithDescriptions.addAll({
e['html_url'] as String: e['description'] != null
? e['description'] as String
: tr('noDescription')
});
}
return urlsWithDescriptions;
} else {
throw getObtainiumHttpError(res);
}
}
}

View File

@ -141,10 +141,13 @@ class GitHub extends AppSource {
if (!includePrereleases && releases[i]['prerelease'] == true) {
continue;
}
var nameToFilter = releases[i]['name'] as String;
if (nameToFilter.trim().isEmpty) {
// Some leave titles empty so tag is used
nameToFilter = releases[i]['tag_name'] as String;
}
if (regexFilter != null &&
!RegExp(regexFilter)
.hasMatch((releases[i]['name'] as String).trim())) {
!RegExp(regexFilter).hasMatch(nameToFilter.trim())) {
continue;
}
var apkUrls = getReleaseAPKUrls(releases[i]);

47
lib/app_sources/html.dart Normal file
View File

@ -0,0 +1,47 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
class HTML extends AppSource {
@override
String standardizeURL(String url) {
return url;
}
@override
String? changeLogPageFromStandardUrl(String standardUrl) => null;
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
var uri = Uri.parse(standardUrl);
Response res = await get(uri);
if (res.statusCode == 200) {
List<String> links = parse(res.body)
.querySelectorAll('a')
.map((element) => element.attributes['href'] ?? '')
.where((element) => element.toLowerCase().endsWith('.apk'))
.toList();
links.sort((a, b) => a.split('/').last.compareTo(b.split('/').last));
if (links.isEmpty) {
throw NoReleasesError();
}
var rel = links.last;
var apkName = rel.split('/').last;
var version = apkName.substring(0, apkName.length - 4);
List<String> apkUrls = [rel]
.map((e) => e.toLowerCase().startsWith('http://') ||
e.toLowerCase().startsWith('https://')
? e
: '${uri.origin}/$e')
.toList();
return APKDetails(version, apkUrls, AppNames(uri.host, tr('app')));
} else {
throw getObtainiumHttpError(res);
}
}
}

View File

@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports
import 'package:easy_localization/src/localization.dart';
const String currentVersion = '0.9.14';
const String currentVersion = '0.10.2';
const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES

View File

@ -24,6 +24,7 @@ class AddAppPage extends StatefulWidget {
class _AddAppPageState extends State<AddAppPage> {
bool gettingAppInfo = false;
bool searching = false;
String userInput = '';
String searchQuery = '';
@ -37,6 +38,8 @@ class _AddAppPageState extends State<AddAppPage> {
SourceProvider sourceProvider = SourceProvider();
AppsProvider appsProvider = context.read<AppsProvider>();
bool doingSomething = gettingAppInfo || searching;
changeUserInput(String input, bool valid, bool isBuilding) {
userInput = input;
if (!isBuilding) {
@ -198,7 +201,7 @@ class _AddAppPageState extends State<AddAppPage> {
gettingAppInfo
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: gettingAppInfo ||
onPressed: doingSomething ||
pickedSource == null ||
(pickedSource!
.combinedAppSpecificSettingFormItems
@ -249,9 +252,12 @@ class _AddAppPageState extends State<AddAppPage> {
width: 16,
),
ElevatedButton(
onPressed: searchQuery.isEmpty || gettingAppInfo
onPressed: searchQuery.isEmpty || doingSomething
? null
: () {
setState(() {
searching = true;
});
Future.wait(sourceProvider.sources
.where((e) => e.canSearch)
.map((e) =>
@ -293,6 +299,10 @@ class _AddAppPageState extends State<AddAppPage> {
}
}).catchError((e) {
showError(e, context);
}).whenComplete(() {
setState(() {
searching = false;
});
});
},
child: Text(tr('search')))

View File

@ -247,10 +247,7 @@ class AppsProvider with ChangeNotifier {
!(await canDowngradeApps())) {
throw DowngradeError();
}
if (appInfo == null ||
int.parse(newInfo.buildNumber) > appInfo.versionCode!) {
await InstallPlugin.installApk(file.file.path, 'dev.imranr.obtainium');
}
await InstallPlugin.installApk(file.file.path, 'dev.imranr.obtainium');
apps[file.appId]!.app.installedVersion =
apps[file.appId]!.app.latestVersion;
// Don't correct install status as installation may not be done yet

View File

@ -7,11 +7,13 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:html/dom.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/apkmirror.dart';
import 'package:obtainium/app_sources/codeberg.dart';
import 'package:obtainium/app_sources/fdroid.dart';
import 'package:obtainium/app_sources/fdroidrepo.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/app_sources/gitlab.dart';
import 'package:obtainium/app_sources/izzyondroid.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/app_sources/mullvad.dart';
import 'package:obtainium/app_sources/signal.dart';
import 'package:obtainium/app_sources/sourceforge.dart';
@ -19,7 +21,6 @@ import 'package:obtainium/app_sources/steammobile.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/mass_app_sources/githubstars.dart';
import 'package:obtainium/providers/settings_provider.dart';
class AppNames {
late String author;
@ -154,6 +155,10 @@ class App {
// Ensure the input is starts with HTTPS and has no WWW
preStandardizeUrl(String url) {
var firstDotIndex = url.indexOf('.');
if (!(firstDotIndex >= 0 && firstDotIndex != url.length - 1)) {
throw UnsupportedURLError();
}
if (url.toLowerCase().indexOf('http://') != 0 &&
url.toLowerCase().indexOf('https://') != 0) {
url = 'https://$url';
@ -269,6 +274,7 @@ class SourceProvider {
List<AppSource> sources = [
GitHub(),
GitLab(),
Codeberg(),
FDroid(),
IzzyOnDroid(),
Mullvad(),
@ -276,7 +282,8 @@ class SourceProvider {
SourceForge(),
APKMirror(),
FDroidRepo(),
SteamMobile()
SteamMobile(),
HTML() // This should ALWAYS be the last option as they are tried in order
];
// Add more mass url source classes here so they are available via the service

View File

@ -182,7 +182,7 @@ packages:
name: file_picker
url: "https://pub.dartlang.org"
source: hosted
version: "5.2.4"
version: "5.2.5"
flutter:
dependency: "direct main"
description: flutter
@ -356,7 +356,7 @@ packages:
name: mime
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
version: "1.0.4"
nested:
dependency: transitive
description:
@ -419,7 +419,7 @@ packages:
name: path_provider_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.6"
version: "2.0.7"
path_provider_platform_interface:
dependency: transitive
description:
@ -531,7 +531,7 @@ packages:
name: shared_preferences
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.15"
version: "2.0.16"
shared_preferences_android:
dependency: transitive
description:
@ -539,10 +539,10 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.14"
shared_preferences_ios:
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_ios
name: shared_preferences_foundation
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
@ -553,13 +553,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
shared_preferences_platform_interface:
dependency: transitive
description:
@ -599,14 +592,14 @@ packages:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.2"
version: "2.2.3"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0+2"
version: "2.4.1"
stack_trace:
dependency: transitive
description:
@ -634,7 +627,7 @@ packages:
name: synchronized
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0+3"
version: "3.0.1"
term_glyph:
dependency: transitive
description:
@ -746,21 +739,21 @@ packages:
name: webview_flutter_android
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.1"
version: "3.1.3"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.1"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
version: "3.0.2"
win32:
dependency: transitive
description:
@ -774,7 +767,7 @@ packages:
name: xdg_directories
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0+2"
version: "0.2.0+3"
xml:
dependency: transitive
description:

View File

@ -17,7 +17,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: 0.9.14+104 # When changing this, update the tag in main() accordingly
version: 0.10.2+108 # When changing this, update the tag in main() accordingly
environment:
sdk: '>=2.18.2 <3.0.0'