Merge pull request #519 from ImranR98/dev

Add Jenkins jobs as a Source (#514), switch to Apps page after App added (#508), Bugfixes (#510)
This commit is contained in:
Imran Remtulla
2023-04-30 17:05:28 -04:00
committed by GitHub
9 changed files with 195 additions and 27 deletions

View File

@@ -17,6 +17,7 @@ Currently supported App sources:
- [SourceForge](https://sourceforge.net/)
- [APKMirror](https://apkmirror.com/) (Track-Only)
- Third Party F-Droid Repos
- Jenkins Jobs
- [Steam](https://store.steampowered.com/mobile)
- [Telegram App](https://telegram.org)
- [VLC](https://www.videolan.org/vlc/download-android.html)

View File

@@ -10,6 +10,66 @@ class HTML extends AppSource {
return url;
}
int compareAlphaNumeric(String a, String b) {
List<String> aParts = _splitAlphaNumeric(a);
List<String> bParts = _splitAlphaNumeric(b);
for (int i = 0; i < aParts.length && i < bParts.length; i++) {
String aPart = aParts[i];
String bPart = bParts[i];
bool aIsNumber = _isNumeric(aPart);
bool bIsNumber = _isNumeric(bPart);
if (aIsNumber && bIsNumber) {
int aNumber = int.parse(aPart);
int bNumber = int.parse(bPart);
int cmp = aNumber.compareTo(bNumber);
if (cmp != 0) {
return cmp;
}
} else if (!aIsNumber && !bIsNumber) {
int cmp = aPart.compareTo(bPart);
if (cmp != 0) {
return cmp;
}
} else {
// Alphanumeric strings come before numeric strings
return aIsNumber ? 1 : -1;
}
}
return aParts.length.compareTo(bParts.length);
}
List<String> _splitAlphaNumeric(String s) {
List<String> parts = [];
StringBuffer sb = StringBuffer();
bool isNumeric = _isNumeric(s[0]);
sb.write(s[0]);
for (int i = 1; i < s.length; i++) {
bool currentIsNumeric = _isNumeric(s[i]);
if (currentIsNumeric == isNumeric) {
sb.write(s[i]);
} else {
parts.add(sb.toString());
sb.clear();
sb.write(s[i]);
isNumeric = currentIsNumeric;
}
}
parts.add(sb.toString());
return parts;
}
bool _isNumeric(String s) {
return s.codeUnitAt(0) >= 48 && s.codeUnitAt(0) <= 57;
}
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl,
@@ -23,7 +83,8 @@ class HTML extends AppSource {
.map((element) => element.attributes['href'] ?? '')
.where((element) => element.toLowerCase().endsWith('.apk'))
.toList();
links.sort((a, b) => a.split('/').last.compareTo(b.split('/').last));
links.sort(
(a, b) => compareAlphaNumeric(a.split('/').last, b.split('/').last));
if (additionalSettings['apkFilterRegEx'] != null) {
var reg = RegExp(additionalSettings['apkFilterRegEx']);
links = links.where((element) => reg.hasMatch(element)).toList();

View File

@@ -0,0 +1,70 @@
import 'dart:convert';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
class Jenkins extends AppSource {
Jenkins() {
overrideVersionDetectionFormDefault('releaseDateAsVersion', true);
}
@override
String trimJobUrl(String url) {
RegExp standardUrlRegEx = RegExp('.*/job/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
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 {
standardUrl = trimJobUrl(standardUrl);
Response res =
await get(Uri.parse('$standardUrl/lastSuccessfulBuild/api/json'));
if (res.statusCode == 200) {
var json = jsonDecode(res.body);
var releaseDate = json['timestamp'] == null
? null
: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int);
var version =
json['number'] == null ? null : (json['number'] as int).toString();
if (version == null) {
throw NoVersionError();
}
var apkUrls = (json['artifacts'] as List<dynamic>)
.map((e) {
var path = (e['relativePath'] as String?);
if (path != null && path.isNotEmpty) {
path = '$standardUrl/lastSuccessfulBuild/artifact/$path';
}
return path == null
? const MapEntry<String, String>('', '')
: MapEntry<String, String>(
(e['fileName'] ?? e['relativePath']) as String, path);
})
.where((url) =>
url.value.isNotEmpty && url.key.toLowerCase().endsWith('.apk'))
.toList();
if (apkUrls.isEmpty) {
throw NoAPKError();
}
return APKDetails(
version,
apkUrls,
releaseDate: releaseDate,
AppNames(Uri.parse(standardUrl).host, standardUrl.split('/').last));
} 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.12.1';
const String currentVersion = '0.12.2';
const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES

View File

@@ -166,7 +166,9 @@ class _AddAppPageState extends State<AddAppPage> {
if (appsProvider.apps.containsKey(app.id)) {
throw ObtainiumError(tr('appAlreadyAdded'));
}
if (app.additionalSettings['trackOnly'] == true) {
if (app.additionalSettings['trackOnly'] == true ||
app.additionalSettings['versionDetection'] !=
'standardVersionDetection') {
app.installedVersion = app.latestVersion;
}
app.categories = pickedCategories;

View File

@@ -6,6 +6,8 @@ import 'package:obtainium/pages/add_app.dart';
import 'package:obtainium/pages/apps.dart';
import 'package:obtainium/pages/import_export.dart';
import 'package:obtainium/pages/settings.dart';
import 'package:obtainium/providers/apps_provider.dart';
import 'package:provider/provider.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@@ -24,6 +26,7 @@ class NavigationPageItem {
class _HomePageState extends State<HomePage> {
List<int> selectedIndexHistory = [];
int prevAppCount = -1;
List<NavigationPageItem> pages = [
NavigationPageItem(tr('appsString'), Icons.apps,
@@ -36,6 +39,39 @@ class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
AppsProvider appsProvider = context.watch<AppsProvider>();
switchToPage(int index) async {
if (index == 0) {
while ((pages[0].widget.key as GlobalKey<AppsPageState>).currentState !=
null) {
// Avoid duplicate GlobalKey error
await Future.delayed(const Duration(microseconds: 1));
}
setState(() {
selectedIndexHistory.clear();
});
} else if (selectedIndexHistory.isEmpty ||
(selectedIndexHistory.isNotEmpty &&
selectedIndexHistory.last != index)) {
setState(() {
int existingInd = selectedIndexHistory.indexOf(index);
if (existingInd >= 0) {
selectedIndexHistory.removeAt(existingInd);
}
selectedIndexHistory.add(index);
});
}
}
if (prevAppCount >= 0 &&
appsProvider.apps.length > prevAppCount &&
selectedIndexHistory.isNotEmpty &&
selectedIndexHistory.last == 1) {
switchToPage(0);
}
prevAppCount = appsProvider.apps.length;
return WillPopScope(
child: Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
@@ -65,27 +101,7 @@ class _HomePageState extends State<HomePage> {
.toList(),
onDestinationSelected: (int index) async {
HapticFeedback.selectionClick();
if (index == 0) {
while ((pages[0].widget.key as GlobalKey<AppsPageState>)
.currentState !=
null) {
// Avoid duplicate GlobalKey error
await Future.delayed(const Duration(microseconds: 1));
}
setState(() {
selectedIndexHistory.clear();
});
} else if (selectedIndexHistory.isEmpty ||
(selectedIndexHistory.isNotEmpty &&
selectedIndexHistory.last != index)) {
setState(() {
int existingInd = selectedIndexHistory.indexOf(index);
if (existingInd >= 0) {
selectedIndexHistory.removeAt(existingInd);
}
selectedIndexHistory.add(index);
});
}
await switchToPage(index);
},
selectedIndex:
selectedIndexHistory.isEmpty ? 0 : selectedIndexHistory.last,

View File

@@ -212,7 +212,7 @@ class AppsProvider with ChangeNotifier {
var fn = file.path.split('/').last;
if (fn.startsWith('${app.id}-') &&
fn.endsWith('.apk') &&
fn != fileName) {
fn != downloadedFile.path.split('/').last) {
file.delete();
}
}

View File

@@ -15,6 +15,7 @@ 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/jenkins.dart';
import 'package:obtainium/app_sources/mullvad.dart';
import 'package:obtainium/app_sources/neutroncode.dart';
import 'package:obtainium/app_sources/signal.dart';
@@ -319,6 +320,22 @@ abstract class AppSource {
name = runtimeType.toString();
}
overrideVersionDetectionFormDefault(String vd, bool disableStandard) {
additionalAppSpecificSourceAgnosticSettingFormItems =
additionalAppSpecificSourceAgnosticSettingFormItems.map((e) {
return e.map((e2) {
if (e2.key == 'versionDetection') {
var item = e2 as GeneratedFormDropdown;
item.defaultValue = vd;
if (disableStandard) {
item.disabledOptKeys = ['standardVersionDetection'];
}
}
return e2;
}).toList();
}).toList();
}
String standardizeUrl(String url) {
url = preStandardizeUrl(url);
if (!hostChanged) {
@@ -341,7 +358,7 @@ abstract class AppSource {
[];
// Some additional data may be needed for Apps regardless of Source
final List<List<GeneratedFormItem>>
List<List<GeneratedFormItem>>
additionalAppSpecificSourceAgnosticSettingFormItems = [
[
GeneratedFormSwitch(
@@ -440,6 +457,7 @@ class SourceProvider {
FDroid(),
IzzyOnDroid(),
FDroidRepo(),
Jenkins(),
SourceForge(),
APKMirror(),
Mullvad(),

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.12.1+161 # When changing this, update the tag in main() accordingly
version: 0.12.2+162 # When changing this, update the tag in main() accordingly
environment:
sdk: '>=2.18.2 <3.0.0'