Merge pull request #567 from ImranR98/dev

Add Tags-Only Support for GitHub (and Codeberg) Track-Only Apps (#566), Increase Size of Changelog Touch Target (#565), Make All Sources Accessible in Override Menu (#543), Other Bugfixes
This commit is contained in:
Imran Remtulla
2023-05-14 13:49:00 -04:00
committed by GitHub
19 changed files with 95 additions and 83 deletions

View File

@@ -1,6 +1,5 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
@@ -85,7 +84,6 @@ class APKCombo extends AppSource {
Map<String, dynamic> additionalSettings, Map<String, dynamic> additionalSettings,
) async { ) async {
String appId = tryInferringAppId(standardUrl)!; String appId = tryInferringAppId(standardUrl)!;
String host = Uri.parse(standardUrl).host;
var preres = await sourceRequest(standardUrl); var preres = await sourceRequest(standardUrl);
if (preres.statusCode != 200) { if (preres.statusCode != 200) {
throw getObtainiumHttpError(preres); throw getObtainiumHttpError(preres);

View File

@@ -1,6 +1,5 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';

View File

@@ -1,6 +1,4 @@
import 'dart:convert';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart'; import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
@@ -9,7 +7,6 @@ import 'package:obtainium/providers/source_provider.dart';
class Codeberg extends AppSource { class Codeberg extends AppSource {
Codeberg() { Codeberg() {
host = 'codeberg.org'; host = 'codeberg.org';
overrideEligible = true;
additionalSourceSpecificSettingFormItems = []; additionalSourceSpecificSettingFormItems = [];
@@ -58,10 +55,10 @@ class Codeberg extends AppSource {
String standardUrl, String standardUrl,
Map<String, dynamic> additionalSettings, Map<String, dynamic> additionalSettings,
) async { ) async {
return gh.getLatestAPKDetailsCommon( return await gh.getLatestAPKDetailsCommon2(standardUrl, additionalSettings,
'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100', (bool useTagUrl) async {
standardUrl, return 'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100';
additionalSettings); }, null);
} }
AppNames getAppNames(String standardUrl) { AppNames getAppNames(String standardUrl) {

View File

@@ -1,6 +1,5 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
@@ -8,7 +7,6 @@ import 'package:obtainium/providers/source_provider.dart';
class FDroidRepo extends AppSource { class FDroidRepo extends AppSource {
FDroidRepo() { FDroidRepo() {
name = tr('fdroidThirdPartyRepo'); name = tr('fdroidThirdPartyRepo');
overrideEligible = true;
additionalSourceAppSpecificSettingFormItems = [ additionalSourceAppSpecificSettingFormItems = [
[ [

View File

@@ -13,7 +13,6 @@ import 'package:url_launcher/url_launcher_string.dart';
class GitHub extends AppSource { class GitHub extends AppSource {
GitHub() { GitHub() {
host = 'github.com'; host = 'github.com';
overrideEligible = true;
additionalSourceSpecificSettingFormItems = [ additionalSourceSpecificSettingFormItems = [
GeneratedFormTextField('github-creds', GeneratedFormTextField('github-creds',
@@ -143,15 +142,17 @@ class GitHub extends AppSource {
} else if (b == null) { } else if (b == null) {
return 1; return 1;
} else { } else {
var stdFormats = findStandardFormatsForVersion(a['tag_name'], true) var nameA = a['tag_name'] ?? a['name'];
.intersection(findStandardFormatsForVersion(b['tag_name'], true)); var nameB = b['tag_name'] ?? b['name'];
var stdFormats = findStandardFormatsForVersion(nameA, true)
.intersection(findStandardFormatsForVersion(nameB, true));
if (stdFormats.isNotEmpty) { if (stdFormats.isNotEmpty) {
var reg = RegExp(stdFormats.first); var reg = RegExp(stdFormats.first);
var matchA = reg.firstMatch(a['tag_name']); var matchA = reg.firstMatch(nameA);
var matchB = reg.firstMatch(b['tag_name']); var matchB = reg.firstMatch(nameB);
return compareAlphaNumeric( return compareAlphaNumeric(
(a['tag_name'] as String).substring(matchA!.start, matchA.end), (nameA as String).substring(matchA!.start, matchA.end),
(b['tag_name'] as String).substring(matchB!.start, matchB.end)); (nameB as String).substring(matchB!.start, matchB.end));
} else { } else {
return getReleaseDateFromRelease(a)! return getReleaseDateFromRelease(a)!
.compareTo(getReleaseDateFromRelease(b)!); .compareTo(getReleaseDateFromRelease(b)!);
@@ -191,7 +192,7 @@ class GitHub extends AppSource {
if (targetRelease == null) { if (targetRelease == null) {
throw NoReleasesError(); throw NoReleasesError();
} }
String? version = targetRelease['tag_name']; String? version = targetRelease['tag_name'] ?? targetRelease['name'];
DateTime? releaseDate = getReleaseDateFromRelease(targetRelease); DateTime? releaseDate = getReleaseDateFromRelease(targetRelease);
if (version == null) { if (version == null) {
throw NoVersionError(); throw NoVersionError();
@@ -211,15 +212,35 @@ class GitHub extends AppSource {
} }
} }
getLatestAPKDetailsCommon2(
String standardUrl,
Map<String, dynamic> additionalSettings,
Future<String> Function(bool) reqUrlGenerator,
dynamic Function(Response)? onHttpErrorCode) async {
try {
return await getLatestAPKDetailsCommon(
await reqUrlGenerator(false), standardUrl, additionalSettings,
onHttpErrorCode: onHttpErrorCode);
} catch (err) {
if (err is NoReleasesError && additionalSettings['trackOnly'] == true) {
return await getLatestAPKDetailsCommon(
await reqUrlGenerator(true), standardUrl, additionalSettings,
onHttpErrorCode: onHttpErrorCode);
} else {
rethrow;
}
}
}
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, String standardUrl,
Map<String, dynamic> additionalSettings, Map<String, dynamic> additionalSettings,
) async { ) async {
return getLatestAPKDetailsCommon( return await getLatestAPKDetailsCommon2(standardUrl, additionalSettings,
'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100', (bool useTagUrl) async {
standardUrl, return 'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100';
additionalSettings, onHttpErrorCode: (Response res) { }, (Response res) {
rateLimitErrorCheck(res); rateLimitErrorCheck(res);
}); });
} }

View File

@@ -14,7 +14,6 @@ import 'package:url_launcher/url_launcher_string.dart';
class GitLab extends AppSource { class GitLab extends AppSource {
GitLab() { GitLab() {
host = 'gitlab.com'; host = 'gitlab.com';
overrideEligible = true;
canSearch = true; canSearch = true;
additionalSourceSpecificSettingFormItems = [ additionalSourceSpecificSettingFormItems = [
@@ -83,12 +82,12 @@ class GitLab extends AppSource {
} }
var json = jsonDecode(res.body) as List<dynamic>; var json = jsonDecode(res.body) as List<dynamic>;
Map<String, List<String>> results = {}; Map<String, List<String>> results = {};
json.forEach((element) { for (var element in json) {
results['https://$host/${element['path_with_namespace']}'] = [ results['https://$host/${element['path_with_namespace']}'] = [
element['name_with_namespace'], element['name_with_namespace'],
element['description'] ?? tr('noDescription') element['description'] ?? tr('noDescription')
]; ];
}); }
return results; return results;
} }

View File

@@ -85,10 +85,6 @@ bool _isNumeric(String s) {
} }
class HTML extends AppSource { class HTML extends AppSource {
HTML() {
overrideEligible = true;
}
@override @override
// TODO: implement requestHeaders choice, hardcoded for now // TODO: implement requestHeaders choice, hardcoded for now
Map<String, String>? get requestHeaders => { Map<String, String>? get requestHeaders => {

View File

@@ -1,4 +1,3 @@
import 'package:http/http.dart';
import 'package:obtainium/app_sources/fdroid.dart'; import 'package:obtainium/app_sources/fdroid.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';

View File

@@ -6,11 +6,9 @@ import 'package:obtainium/providers/source_provider.dart';
class Jenkins extends AppSource { class Jenkins extends AppSource {
Jenkins() { Jenkins() {
overrideEligible = true;
overrideVersionDetectionFormDefault('releaseDateAsVersion', true); overrideVersionDetectionFormDefault('releaseDateAsVersion', true);
} }
@override
String trimJobUrl(String url) { String trimJobUrl(String url) {
RegExp standardUrlRegEx = RegExp('.*/job/[^/]+'); RegExp standardUrlRegEx = RegExp('.*/job/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url); RegExpMatch? match = standardUrlRegEx.firstMatch(url);

View File

@@ -6,7 +6,6 @@ import 'package:obtainium/providers/source_provider.dart';
class SourceForge extends AppSource { class SourceForge extends AppSource {
SourceForge() { SourceForge() {
host = 'sourceforge.net'; host = 'sourceforge.net';
overrideEligible = true;
} }
@override @override

View File

@@ -1,6 +1,5 @@
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/app_sources/html.dart'; import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
@@ -10,7 +9,6 @@ import 'package:easy_localization/easy_localization.dart';
class SourceHut extends AppSource { class SourceHut extends AppSource {
SourceHut() { SourceHut() {
host = 'git.sr.ht'; host = 'git.sr.ht';
overrideEligible = true;
additionalSourceAppSpecificSettingFormItems = [ additionalSourceAppSpecificSettingFormItems = [
[ [
@@ -58,11 +56,19 @@ class SourceHut extends AppSource {
throw NoVersionError(); throw NoVersionError();
} }
String? releaseDateString = entry.querySelector('pubDate')?.innerHtml; String? releaseDateString = entry.querySelector('pubDate')?.innerHtml;
var link = entry.querySelector('link');
String releasePage = '$standardUrl/refs/$version'; String releasePage = '$standardUrl/refs/$version';
DateTime? releaseDate = releaseDateString != null DateTime? releaseDate;
? DateFormat('EEE, dd MMM yyyy HH:mm:ss Z').parse(releaseDateString) try {
: null; releaseDate = releaseDateString != null
? DateFormat('E, dd MMM yyyy HH:mm:ss Z').parse(releaseDateString)
: null;
releaseDate = releaseDateString != null
? DateFormat('EEE, dd MMM yyyy HH:mm:ss Z')
.parse(releaseDateString)
: null;
} catch (e) {
// ignore
}
var res2 = await sourceRequest(releasePage); var res2 = await sourceRequest(releasePage);
List<MapEntry<String, String>> apkUrls = []; List<MapEntry<String, String>> apkUrls = [];
if (res2.statusCode == 200) { if (res2.statusCode == 200) {

View File

@@ -1,6 +1,5 @@
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';

View File

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

View File

@@ -321,10 +321,8 @@ class _AddAppPageState extends State<AddAppPage> {
'overrideSource', 'overrideSource',
defaultValue: HTML().runtimeType.toString(), defaultValue: HTML().runtimeType.toString(),
[ [
...sourceProvider.sources ...sourceProvider.sources.map(
.where((s) => s.overrideEligible) (s) => MapEntry(s.runtimeType.toString(), s.name))
.map((s) =>
MapEntry(s.runtimeType.toString(), s.name))
], ],
label: tr('overrideSource')) label: tr('overrideSource'))
] ]

View File

@@ -32,6 +32,7 @@ class _AppPageState extends State<AppPage> {
getUpdate(String id) { getUpdate(String id) {
appsProvider.checkUpdate(id).catchError((e) { appsProvider.checkUpdate(id).catchError((e) {
showError(e, context); showError(e, context);
return null;
}); });
} }

View File

@@ -71,6 +71,7 @@ class AppsPageState extends State<AppsPage> {
}); });
return appsProvider.checkUpdates().catchError((e) { return appsProvider.checkUpdates().catchError((e) {
showError(e, context); showError(e, context);
return <App>[];
}).whenComplete(() { }).whenComplete(() {
setState(() { setState(() {
refreshingSince = null; refreshingSince = null;
@@ -379,6 +380,7 @@ class AppsPageState extends State<AppsPage> {
[listedApps[appIndex].app.id], [listedApps[appIndex].app.id],
globalNavigatorKey.currentContext).catchError((e) { globalNavigatorKey.currentContext).catchError((e) {
showError(e, context); showError(e, context);
return <String>[];
}); });
}, },
icon: Icon( icon: Icon(
@@ -441,37 +443,35 @@ class AppsPageState extends State<AppsPage> {
width: 10, width: 10,
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),
Column( GestureDetector(
mainAxisAlignment: MainAxisAlignment.center, onTap: showChangesFn,
crossAxisAlignment: CrossAxisAlignment.end, child: Column(
children: [ mainAxisAlignment: MainAxisAlignment.center,
Row(mainAxisSize: MainAxisSize.min, children: [ crossAxisAlignment: CrossAxisAlignment.end,
Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width / 4),
child: Text(
getVersionText(index),
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end,
)),
]),
Row(
mainAxisSize: MainAxisSize.min,
children: [ children: [
GestureDetector( Row(mainAxisSize: MainAxisSize.min, children: [
onTap: showChangesFn, Container(
child: Text( constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width / 4),
child: Text(getVersionText(index),
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end)),
]),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
getChangesButtonString(index, showChangesFn != null), getChangesButtonString(index, showChangesFn != null),
style: TextStyle( style: TextStyle(
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
decoration: showChangesFn != null decoration: showChangesFn != null
? TextDecoration.underline ? TextDecoration.underline
: TextDecoration.none), : TextDecoration.none),
)) )
],
),
], ],
), ))
],
)
], ],
); );
@@ -540,15 +540,20 @@ class AppsPageState extends State<AppsPage> {
: FontWeight.normal)), : FontWeight.normal)),
trailing: listedApps[index].downloadProgress != null trailing: listedApps[index].downloadProgress != null
? SizedBox( ? SizedBox(
width: 110, width: 90,
child: Text(tr('percentProgress', args: [ child: Text(
listedApps[index].downloadProgress! >= 0 listedApps[index].downloadProgress! >= 0
? listedApps[index] ? tr('percentProgress', args: [
.downloadProgress! listedApps[index]
.toInt() .downloadProgress!
.toString() .toInt()
: tr('pleaseWait') .toString()
]))) ])
: tr('pleaseWait'),
textAlign: (listedApps[index].downloadProgress! >= 0)
? TextAlign.start
: TextAlign.end,
))
: trailingRow, : trailingRow,
onTap: () { onTap: () {
if (selectedAppIds.isNotEmpty) { if (selectedAppIds.isNotEmpty) {
@@ -681,6 +686,7 @@ class AppsPageState extends State<AppsPage> {
settingsProvider: settingsProvider) settingsProvider: settingsProvider)
.catchError((e) { .catchError((e) {
showError(e, context); showError(e, context);
return <String>[];
}); });
} }
}); });

View File

@@ -323,8 +323,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
], ],
), ),
if (importInProgress) if (importInProgress)
Column( const Column(
children: const [ children: [
SizedBox( SizedBox(
height: 14, height: 14,
), ),

View File

@@ -7,7 +7,6 @@ import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:html/dom.dart'; import 'package:html/dom.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:obtainium/app_sources/apkcombo.dart';
import 'package:obtainium/app_sources/apkmirror.dart'; import 'package:obtainium/app_sources/apkmirror.dart';
import 'package:obtainium/app_sources/apkpure.dart'; import 'package:obtainium/app_sources/apkpure.dart';
import 'package:obtainium/app_sources/codeberg.dart'; import 'package:obtainium/app_sources/codeberg.dart';
@@ -318,7 +317,6 @@ abstract class AppSource {
late String name; late String name;
bool enforceTrackOnly = false; bool enforceTrackOnly = false;
bool changeLogIfAnyIsMarkDown = true; bool changeLogIfAnyIsMarkDown = true;
bool overrideEligible = false;
AppSource() { AppSource() {
name = runtimeType.toString(); name = runtimeType.toString();

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 # 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 # 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. # of the product and file versions while build-number is used as the build suffix.
version: 0.13.3+167 # When changing this, update the tag in main() accordingly version: 0.13.4+168 # When changing this, update the tag in main() accordingly
environment: environment:
sdk: '>=2.18.2 <3.0.0' sdk: '>=2.18.2 <3.0.0'