mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-08-22 05:59:30 +02:00
Merge pull request #1696 from ImranR98/dev
- Bugfix: Pull to refresh not working with few apps (#1680) - Add third-party F-Droid repo search to main search menu (#1681) - Added autocomplete for F-Droid repos (#1681) - Bugfix: Missing request headers for direct APK link apps (#1688) - Release asset download confirmation even for single choice (#1694) - Add a less obvious touch target to highlights (#1694)
This commit is contained in:
@@ -24,6 +24,14 @@ class DirectAPKLink extends AppSource {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, String>?> getRequestHeaders(
|
||||||
|
Map<String, dynamic> additionalSettings,
|
||||||
|
{bool forAPKDownload = false}) {
|
||||||
|
return html.getRequestHeaders(additionalSettings,
|
||||||
|
forAPKDownload: forAPKDownload);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl,
|
String standardUrl,
|
||||||
|
@@ -9,7 +9,7 @@ class FDroidRepo extends AppSource {
|
|||||||
FDroidRepo() {
|
FDroidRepo() {
|
||||||
name = tr('fdroidThirdPartyRepo');
|
name = tr('fdroidThirdPartyRepo');
|
||||||
canSearch = true;
|
canSearch = true;
|
||||||
excludeFromMassSearch = true;
|
includeAdditionalOptsInMainSearch = true;
|
||||||
neverAutoSelect = true;
|
neverAutoSelect = true;
|
||||||
showReleaseDateAsVersionToggle = true;
|
showReleaseDateAsVersionToggle = true;
|
||||||
|
|
||||||
@@ -86,6 +86,27 @@ class FDroidRepo extends AppSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void runOnAddAppInputChange(String userInput) {
|
||||||
|
this.additionalSourceAppSpecificSettingFormItems =
|
||||||
|
this.additionalSourceAppSpecificSettingFormItems.map((row) {
|
||||||
|
row = row.map((item) {
|
||||||
|
if (item.key == 'appIdOrName') {
|
||||||
|
try {
|
||||||
|
var appId = Uri.parse(userInput).queryParameters['appId'];
|
||||||
|
if (appId != null && item is GeneratedFormTextField) {
|
||||||
|
item.required = false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}).toList();
|
||||||
|
return row;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
App endOfGetAppChanges(App app) {
|
App endOfGetAppChanges(App app) {
|
||||||
var uri = Uri.parse(app.url);
|
var uri = Uri.parse(app.url);
|
||||||
@@ -142,6 +163,7 @@ class FDroidRepo extends AppSource {
|
|||||||
if (appIdOrName == null) {
|
if (appIdOrName == null) {
|
||||||
throw NoReleasesError();
|
throw NoReleasesError();
|
||||||
}
|
}
|
||||||
|
additionalSettings['appIdOrName'] = appIdOrName;
|
||||||
var res =
|
var res =
|
||||||
await sourceRequestWithURLVariants(standardUrl, additionalSettings);
|
await sourceRequestWithURLVariants(standardUrl, additionalSettings);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
|
@@ -332,10 +332,13 @@ class HTML extends AppSource {
|
|||||||
additionalSettings['versionExtractWholePage'] == true
|
additionalSettings['versionExtractWholePage'] == true
|
||||||
? versionExtractionWholePageString
|
? versionExtractionWholePageString
|
||||||
: relDecoded);
|
: relDecoded);
|
||||||
version ??=
|
version ??= additionalSettings['defaultPseudoVersioningMethod'] ==
|
||||||
additionalSettings['defaultPseudoVersioningMethod'] == 'APKLinkHash'
|
'APKLinkHash'
|
||||||
? rel.hashCode.toString()
|
? rel.hashCode.toString()
|
||||||
: (await checkPartialDownloadHashDynamic(rel)).toString();
|
: (await checkPartialDownloadHashDynamic(rel,
|
||||||
|
headers: await getRequestHeaders(additionalSettings,
|
||||||
|
forAPKDownload: true)))
|
||||||
|
.toString();
|
||||||
return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(),
|
return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(),
|
||||||
AppNames(uri.host, tr('app')));
|
AppNames(uri.host, tr('app')));
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:obtainium/components/generated_form_modal.dart';
|
import 'package:obtainium/components/generated_form_modal.dart';
|
||||||
import 'package:obtainium/providers/source_provider.dart';
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||||
|
|
||||||
abstract class GeneratedFormItem {
|
abstract class GeneratedFormItem {
|
||||||
late String key;
|
late String key;
|
||||||
@@ -28,6 +29,7 @@ class GeneratedFormTextField extends GeneratedFormItem {
|
|||||||
late String? hint;
|
late String? hint;
|
||||||
late bool password;
|
late bool password;
|
||||||
late TextInputType? textInputType;
|
late TextInputType? textInputType;
|
||||||
|
late List<String>? autoCompleteOptions;
|
||||||
|
|
||||||
GeneratedFormTextField(super.key,
|
GeneratedFormTextField(super.key,
|
||||||
{super.label,
|
{super.label,
|
||||||
@@ -39,7 +41,8 @@ class GeneratedFormTextField extends GeneratedFormItem {
|
|||||||
this.max = 1,
|
this.max = 1,
|
||||||
this.hint,
|
this.hint,
|
||||||
this.password = false,
|
this.password = false,
|
||||||
this.textInputType});
|
this.textInputType,
|
||||||
|
this.autoCompleteOptions});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String ensureType(val) {
|
String ensureType(val) {
|
||||||
@@ -274,38 +277,62 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
var formItem = e.value;
|
var formItem = e.value;
|
||||||
if (formItem is GeneratedFormTextField) {
|
if (formItem is GeneratedFormTextField) {
|
||||||
final formFieldKey = GlobalKey<FormFieldState>();
|
final formFieldKey = GlobalKey<FormFieldState>();
|
||||||
return TextFormField(
|
var ctrl = TextEditingController(text: values[formItem.key]);
|
||||||
keyboardType: formItem.textInputType,
|
return TypeAheadField<String>(
|
||||||
obscureText: formItem.password,
|
controller: ctrl,
|
||||||
autocorrect: !formItem.password,
|
builder: (context, controller, focusNode) {
|
||||||
enableSuggestions: !formItem.password,
|
return TextFormField(
|
||||||
key: formFieldKey,
|
controller: ctrl,
|
||||||
initialValue: values[formItem.key],
|
focusNode: focusNode,
|
||||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
keyboardType: formItem.textInputType,
|
||||||
onChanged: (value) {
|
obscureText: formItem.password,
|
||||||
|
autocorrect: !formItem.password,
|
||||||
|
enableSuggestions: !formItem.password,
|
||||||
|
key: formFieldKey,
|
||||||
|
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
values[formItem.key] = value;
|
||||||
|
someValueChanged();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
helperText:
|
||||||
|
formItem.label + (formItem.required ? ' *' : ''),
|
||||||
|
hintText: formItem.hint),
|
||||||
|
minLines: formItem.max <= 1 ? null : formItem.max,
|
||||||
|
maxLines: formItem.max <= 1 ? 1 : formItem.max,
|
||||||
|
validator: (value) {
|
||||||
|
if (formItem.required &&
|
||||||
|
(value == null || value.trim().isEmpty)) {
|
||||||
|
return '${formItem.label} ${tr('requiredInBrackets')}';
|
||||||
|
}
|
||||||
|
for (var validator in formItem.additionalValidators) {
|
||||||
|
String? result = validator(value);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemBuilder: (context, value) {
|
||||||
|
return ListTile(title: Text(value));
|
||||||
|
},
|
||||||
|
onSelected: (value) {
|
||||||
|
ctrl.text = value;
|
||||||
setState(() {
|
setState(() {
|
||||||
values[formItem.key] = value;
|
values[formItem.key] = value;
|
||||||
someValueChanged();
|
someValueChanged();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
suggestionsCallback: (search) {
|
||||||
helperText: formItem.label + (formItem.required ? ' *' : ''),
|
return formItem.autoCompleteOptions
|
||||||
hintText: formItem.hint),
|
?.where((t) => t.toLowerCase().contains(search.toLowerCase()))
|
||||||
minLines: formItem.max <= 1 ? null : formItem.max,
|
.toList();
|
||||||
maxLines: formItem.max <= 1 ? 1 : formItem.max,
|
|
||||||
validator: (value) {
|
|
||||||
if (formItem.required &&
|
|
||||||
(value == null || value.trim().isEmpty)) {
|
|
||||||
return '${formItem.label} ${tr('requiredInBrackets')}';
|
|
||||||
}
|
|
||||||
for (var validator in formItem.additionalValidators) {
|
|
||||||
String? result = validator(value);
|
|
||||||
if (result != null) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
},
|
||||||
|
hideOnEmpty: true,
|
||||||
);
|
);
|
||||||
} else if (formItem is GeneratedFormDropdown) {
|
} else if (formItem is GeneratedFormDropdown) {
|
||||||
if (formItem.opts!.isEmpty) {
|
if (formItem.opts!.isEmpty) {
|
||||||
|
@@ -51,10 +51,13 @@ class AddAppPageState extends State<AddAppPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
changeUserInput(String input, bool valid, bool isBuilding,
|
changeUserInput(String input, bool valid, bool isBuilding,
|
||||||
{bool updateUrlInput = false}) {
|
{bool updateUrlInput = false, String? overrideSource}) {
|
||||||
userInput = input;
|
userInput = input;
|
||||||
if (!isBuilding) {
|
if (!isBuilding) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
if (overrideSource != null) {
|
||||||
|
pickedSourceOverride = overrideSource;
|
||||||
|
}
|
||||||
if (updateUrlInput) {
|
if (updateUrlInput) {
|
||||||
urlInputKey++;
|
urlInputKey++;
|
||||||
}
|
}
|
||||||
@@ -68,6 +71,7 @@ class AddAppPageState extends State<AddAppPage> {
|
|||||||
if (pickedSource.runtimeType != source.runtimeType ||
|
if (pickedSource.runtimeType != source.runtimeType ||
|
||||||
(prevHost != null && prevHost != source?.hosts[0])) {
|
(prevHost != null && prevHost != source?.hosts[0])) {
|
||||||
pickedSource = source;
|
pickedSource = source;
|
||||||
|
pickedSource?.runOnAddAppInputChange(userInput);
|
||||||
additionalSettings = source != null
|
additionalSettings = source != null
|
||||||
? getDefaultValuesFromFormItems(
|
? getDefaultValuesFromFormItems(
|
||||||
source.combinedAppSpecificSettingFormItems)
|
source.combinedAppSpecificSettingFormItems)
|
||||||
@@ -259,9 +263,7 @@ class AddAppPageState extends State<AddAppPage> {
|
|||||||
searching = true;
|
searching = true;
|
||||||
});
|
});
|
||||||
var sourceStrings = <String, List<String>>{};
|
var sourceStrings = <String, List<String>>{};
|
||||||
sourceProvider.sources
|
sourceProvider.sources.where((e) => e.canSearch).forEach((s) {
|
||||||
.where((e) => e.canSearch && !e.excludeFromMassSearch)
|
|
||||||
.forEach((s) {
|
|
||||||
sourceStrings[s.name] = [s.name];
|
sourceStrings[s.name] = [s.name];
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
@@ -282,32 +284,78 @@ class AddAppPageState extends State<AddAppPage> {
|
|||||||
settingsProvider.searchDeselected = sourceStrings.keys
|
settingsProvider.searchDeselected = sourceStrings.keys
|
||||||
.where((s) => !searchSources.contains(s))
|
.where((s) => !searchSources.contains(s))
|
||||||
.toList();
|
.toList();
|
||||||
var results = await Future.wait(sourceProvider.sources
|
List<MapEntry<String, Map<String, List<String>>>?> results =
|
||||||
.where((e) => searchSources.contains(e.name))
|
(await Future.wait(sourceProvider.sources
|
||||||
.map((e) async {
|
.where((e) => searchSources.contains(e.name))
|
||||||
|
.map((e) async {
|
||||||
try {
|
try {
|
||||||
return await e.search(searchQuery);
|
Map<String, dynamic>? querySettings = {};
|
||||||
|
if (e.includeAdditionalOptsInMainSearch) {
|
||||||
|
querySettings = await showDialog<Map<String, dynamic>?>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext ctx) {
|
||||||
|
return GeneratedFormModal(
|
||||||
|
title: tr('searchX', args: [e.name]),
|
||||||
|
items: [
|
||||||
|
...e.searchQuerySettingFormItems.map((e) => [e]),
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('url',
|
||||||
|
label: e.hosts.isNotEmpty
|
||||||
|
? tr('overrideSource')
|
||||||
|
: plural('url', 1).substring(2),
|
||||||
|
autoCompleteOptions: [
|
||||||
|
...(e.hosts.isNotEmpty ? [e.hosts[0]] : []),
|
||||||
|
...appsProvider.apps.values
|
||||||
|
.where((a) =>
|
||||||
|
sourceProvider
|
||||||
|
.getSource(a.app.url,
|
||||||
|
overrideSource:
|
||||||
|
a.app.overrideSource)
|
||||||
|
.runtimeType ==
|
||||||
|
e.runtimeType)
|
||||||
|
.map((a) {
|
||||||
|
var uri = Uri.parse(a.app.url);
|
||||||
|
return '${uri.origin}${uri.path}';
|
||||||
|
})
|
||||||
|
],
|
||||||
|
defaultValue:
|
||||||
|
e.hosts.isNotEmpty ? e.hosts[0] : '',
|
||||||
|
required: true)
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (querySettings == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MapEntry(e.runtimeType.toString(),
|
||||||
|
await e.search(searchQuery, querySettings: querySettings));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err is! CredsNeededError) {
|
if (err is! CredsNeededError) {
|
||||||
rethrow;
|
rethrow;
|
||||||
} else {
|
} else {
|
||||||
err.unexpected = true;
|
err.unexpected = true;
|
||||||
showError(err, context);
|
showError(err, context);
|
||||||
return <String, List<String>>{};
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
})))
|
||||||
|
.where((a) => a != null)
|
||||||
|
.toList();
|
||||||
|
|
||||||
// Interleave results instead of simple reduce
|
// Interleave results instead of simple reduce
|
||||||
Map<String, List<String>> res = {};
|
Map<String, MapEntry<String, List<String>>> res = {};
|
||||||
var si = 0;
|
var si = 0;
|
||||||
var done = false;
|
var done = false;
|
||||||
while (!done) {
|
while (!done) {
|
||||||
done = true;
|
done = true;
|
||||||
for (var r in results) {
|
for (var r in results) {
|
||||||
if (r.length > si) {
|
var sourceName = r!.key;
|
||||||
|
if (r.value.length > si) {
|
||||||
done = false;
|
done = false;
|
||||||
res.addEntries([r.entries.elementAt(si)]);
|
var singleRes = r.value.entries.elementAt(si);
|
||||||
|
res[singleRes.key] = MapEntry(sourceName, singleRes.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
si++;
|
si++;
|
||||||
@@ -322,13 +370,15 @@ class AddAppPageState extends State<AddAppPage> {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext ctx) {
|
builder: (BuildContext ctx) {
|
||||||
return SelectionModal(
|
return SelectionModal(
|
||||||
entries: res,
|
entries: res.map((k, v) => MapEntry(k, v.value)),
|
||||||
selectedByDefault: false,
|
selectedByDefault: false,
|
||||||
onlyOneSelectionAllowed: true,
|
onlyOneSelectionAllowed: true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (selectedUrls != null && selectedUrls.isNotEmpty) {
|
if (selectedUrls != null && selectedUrls.isNotEmpty) {
|
||||||
changeUserInput(selectedUrls[0], true, false, updateUrlInput: true);
|
var sourceName = res[selectedUrls[0]]?.key;
|
||||||
|
changeUserInput(selectedUrls[0], true, false,
|
||||||
|
updateUrlInput: true, overrideSource: sourceName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -349,7 +399,7 @@ class AddAppPageState extends State<AddAppPage> {
|
|||||||
[
|
[
|
||||||
GeneratedFormDropdown(
|
GeneratedFormDropdown(
|
||||||
'overrideSource',
|
'overrideSource',
|
||||||
defaultValue: '',
|
defaultValue: pickedSourceOverride ?? '',
|
||||||
[
|
[
|
||||||
MapEntry('', tr('none')),
|
MapEntry('', tr('none')),
|
||||||
...sourceProvider.sources.map(
|
...sourceProvider.sources.map(
|
||||||
|
@@ -161,25 +161,46 @@ class _AppPageState extends State<AppPage> {
|
|||||||
if (app?.app.apkUrls.isNotEmpty == true ||
|
if (app?.app.apkUrls.isNotEmpty == true ||
|
||||||
app?.app.otherAssetUrls.isNotEmpty == true)
|
app?.app.otherAssetUrls.isNotEmpty == true)
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: app?.app == null || updating
|
onTap: app?.app == null || updating
|
||||||
? null
|
? null
|
||||||
: () async {
|
: () async {
|
||||||
try {
|
try {
|
||||||
await appsProvider
|
await appsProvider
|
||||||
.downloadAppAssets([app!.app.id], context);
|
.downloadAppAssets([app!.app.id], context);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showError(e, context);
|
showError(e, context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Row(
|
||||||
tr('downloadX', args: [tr('releaseAsset').toLowerCase()]),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
textAlign: TextAlign.center,
|
children: [
|
||||||
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
Container(
|
||||||
decoration: TextDecoration.underline,
|
decoration: BoxDecoration(
|
||||||
fontStyle: FontStyle.italic,
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
color: settingsProvider.highlightTouchTargets
|
||||||
),
|
? (Theme.of(context).brightness ==
|
||||||
),
|
Brightness.light
|
||||||
|
? Theme.of(context).primaryColor
|
||||||
|
: Theme.of(context).primaryColorLight)
|
||||||
|
.withAlpha(20)
|
||||||
|
: null),
|
||||||
|
padding: settingsProvider.highlightTouchTargets
|
||||||
|
? const EdgeInsetsDirectional.fromSTEB(12, 6, 12, 6)
|
||||||
|
: const EdgeInsetsDirectional.fromSTEB(0, 6, 0, 6),
|
||||||
|
margin:
|
||||||
|
const EdgeInsetsDirectional.fromSTEB(0, 6, 0, 0),
|
||||||
|
child: Text(
|
||||||
|
tr('downloadX',
|
||||||
|
args: [tr('releaseAsset').toLowerCase()]),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.labelSmall!.copyWith(
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
],
|
||||||
|
)),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 48,
|
height: 48,
|
||||||
),
|
),
|
||||||
|
@@ -466,7 +466,7 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
hasUpdate ? getUpdateButton(index) : const SizedBox.shrink(),
|
hasUpdate ? getUpdateButton(index) : const SizedBox.shrink(),
|
||||||
hasUpdate
|
hasUpdate
|
||||||
? const SizedBox(
|
? const SizedBox(
|
||||||
width: 10,
|
width: 5,
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
@@ -1105,6 +1105,7 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
interactive: true,
|
interactive: true,
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
CustomAppBar(title: tr('appsString')),
|
CustomAppBar(title: tr('appsString')),
|
||||||
|
@@ -717,7 +717,8 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<MapEntry<String, String>?> confirmAppFileUrl(
|
Future<MapEntry<String, String>?> confirmAppFileUrl(
|
||||||
App app, BuildContext? context, bool pickAnyAsset) async {
|
App app, BuildContext? context, bool pickAnyAsset,
|
||||||
|
{bool evenIfSingleChoice = false}) async {
|
||||||
var urlsToSelectFrom = app.apkUrls;
|
var urlsToSelectFrom = app.apkUrls;
|
||||||
if (pickAnyAsset) {
|
if (pickAnyAsset) {
|
||||||
urlsToSelectFrom = [...urlsToSelectFrom, ...app.otherAssetUrls];
|
urlsToSelectFrom = [...urlsToSelectFrom, ...app.otherAssetUrls];
|
||||||
@@ -728,7 +729,8 @@ class AppsProvider with ChangeNotifier {
|
|||||||
// get device supported architecture
|
// get device supported architecture
|
||||||
List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis;
|
List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis;
|
||||||
|
|
||||||
if (urlsToSelectFrom.length > 1 && context != null) {
|
if ((urlsToSelectFrom.length > 1 || evenIfSingleChoice) &&
|
||||||
|
context != null) {
|
||||||
appFileUrl = await showDialog(
|
appFileUrl = await showDialog(
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
context: context,
|
context: context,
|
||||||
@@ -973,7 +975,8 @@ class AppsProvider with ChangeNotifier {
|
|||||||
if (apps[id]!.app.apkUrls.isNotEmpty ||
|
if (apps[id]!.app.apkUrls.isNotEmpty ||
|
||||||
apps[id]!.app.otherAssetUrls.isNotEmpty) {
|
apps[id]!.app.otherAssetUrls.isNotEmpty) {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
fileUrl = await confirmAppFileUrl(apps[id]!.app, context, true);
|
fileUrl = await confirmAppFileUrl(apps[id]!.app, context, true,
|
||||||
|
evenIfSingleChoice: true);
|
||||||
}
|
}
|
||||||
if (fileUrl != null) {
|
if (fileUrl != null) {
|
||||||
filesToDownload.add(MapEntry(fileUrl, apps[id]!.app));
|
filesToDownload.add(MapEntry(fileUrl, apps[id]!.app));
|
||||||
@@ -1650,7 +1653,9 @@ class _AppFilePickerState extends State<AppFilePicker> {
|
|||||||
? tr('selectX', args: [tr('releaseAsset').toLowerCase()])
|
? tr('selectX', args: [tr('releaseAsset').toLowerCase()])
|
||||||
: tr('pickAnAPK')),
|
: tr('pickAnAPK')),
|
||||||
content: Column(children: [
|
content: Column(children: [
|
||||||
Text(tr('appHasMoreThanOnePackage', args: [widget.app.finalName])),
|
urlsToSelectFrom.length > 1
|
||||||
|
? Text(tr('appHasMoreThanOnePackage', args: [widget.app.finalName]))
|
||||||
|
: const SizedBox.shrink(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
...urlsToSelectFrom.map(
|
...urlsToSelectFrom.map(
|
||||||
(u) => RadioListTile<String>(
|
(u) => RadioListTile<String>(
|
||||||
|
@@ -354,7 +354,9 @@ preStandardizeUrl(String url) {
|
|||||||
url.toLowerCase().indexOf('https://') != 0) {
|
url.toLowerCase().indexOf('https://') != 0) {
|
||||||
url = 'https://$url';
|
url = 'https://$url';
|
||||||
}
|
}
|
||||||
var trailingSlash = Uri.tryParse(url)?.path.endsWith('/') ?? false;
|
var uri = Uri.tryParse(url);
|
||||||
|
var trailingSlash = (uri?.path.endsWith('/') ?? false) &&
|
||||||
|
(uri?.queryParameters.isEmpty ?? false);
|
||||||
url = url
|
url = url
|
||||||
.split('/')
|
.split('/')
|
||||||
.where((e) => e.isNotEmpty)
|
.where((e) => e.isNotEmpty)
|
||||||
@@ -463,6 +465,10 @@ abstract class AppSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void runOnAddAppInputChange(String inputUrl) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
throw NotImplementedError();
|
throw NotImplementedError();
|
||||||
}
|
}
|
||||||
@@ -617,7 +623,7 @@ abstract class AppSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool canSearch = false;
|
bool canSearch = false;
|
||||||
bool excludeFromMassSearch = false;
|
bool includeAdditionalOptsInMainSearch = false;
|
||||||
List<GeneratedFormItem> searchQuerySettingFormItems = [];
|
List<GeneratedFormItem> searchQuerySettingFormItems = [];
|
||||||
Future<Map<String, List<String>>> search(String query,
|
Future<Map<String, List<String>>> search(String query,
|
||||||
{Map<String, dynamic> querySettings = const {}}) {
|
{Map<String, dynamic> querySettings = const {}}) {
|
||||||
|
140
pubspec.lock
140
pubspec.lock
@@ -47,10 +47,34 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: app_links
|
name: app_links
|
||||||
sha256: "96e677810b83707ff5e10fac11e4839daa0ea4e0123c35864c092699165eb3db"
|
sha256: a9905d6a60e814503fabc7523a9ed161b812d7ca69c99ad8ceea14279dc4f06b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.1"
|
version: "6.1.3"
|
||||||
|
app_links_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: app_links_linux
|
||||||
|
sha256: "567139eca3ca9fb113f2082f3aaa75a26f30f0ebdbe5fa7f09a3913c5bebd630"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
|
app_links_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: app_links_platform_interface
|
||||||
|
sha256: "58cff6f11df59b0e514dd5e4a61e988348ad5662f0e75d45d4e214ebea55c94c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
app_links_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: app_links_web
|
||||||
|
sha256: "74586ed5f3c4786341e82a0fa43c39ec3f13108a550f74e80d8bf68aa11349d1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -279,18 +303,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flex_color_picker
|
name: flex_color_picker
|
||||||
sha256: "31b27677d8d8400e4cff5edb3f189f606dd964d608779b6ae1b7ddad37ea48c6"
|
sha256: "809af4ec82ede3b140ed0219b97d548de99e47aa4b99b14a10f705a2dbbcba5e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.5.0"
|
version: "3.5.1"
|
||||||
flex_seed_scheme:
|
flex_seed_scheme:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flex_seed_scheme
|
name: flex_seed_scheme
|
||||||
sha256: fb66cdb8ca89084e79efcad2bc2d9deb144666875116f08cdd8d9f8238c8b3ab
|
sha256: "6c595e545b0678e1fe17e8eec3d1fbca7237482da194fadc20ad8607dc7a7f3d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "3.0.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -312,6 +336,54 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0"
|
version: "0.3.0"
|
||||||
|
flutter_keyboard_visibility:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_keyboard_visibility
|
||||||
|
sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
|
flutter_keyboard_visibility_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_keyboard_visibility_linux
|
||||||
|
sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
flutter_keyboard_visibility_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_keyboard_visibility_macos
|
||||||
|
sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
flutter_keyboard_visibility_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_keyboard_visibility_platform_interface
|
||||||
|
sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
flutter_keyboard_visibility_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_keyboard_visibility_web
|
||||||
|
sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
flutter_keyboard_visibility_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_keyboard_visibility_windows
|
||||||
|
sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -332,10 +404,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_local_notifications
|
name: flutter_local_notifications
|
||||||
sha256: "40e6fbd2da7dcc7ed78432c5cdab1559674b4af035fddbfb2f9a8f9c2112fcef"
|
sha256: ced76d337f54de33d7d9f06092137b4ac2da5079e00cee8a11a1794ffc7c61c6
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "17.1.2"
|
version: "17.2.1"
|
||||||
flutter_local_notifications_linux:
|
flutter_local_notifications_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -348,10 +420,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_local_notifications_platform_interface
|
name: flutter_local_notifications_platform_interface
|
||||||
sha256: "340abf67df238f7f0ef58f4a26d2a83e1ab74c77ab03cd2b2d5018ac64db30b7"
|
sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.1.0"
|
version: "7.2.0"
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -361,10 +433,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_markdown
|
name: flutter_markdown
|
||||||
sha256: ff76a9300a06ad1f2b394e54c0b4beaaf6a95f95c98540c918b870221499bb10
|
sha256: "2e8a801b1ded5ea001a4529c97b1f213dcb11c6b20668e081cafb23468593514"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.2"
|
version: "0.7.3"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -378,6 +450,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_typeahead:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_typeahead
|
||||||
|
sha256: d64712c65db240b1057559b952398ebb6e498077baeebf9b0731dade62438a6d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.2.0"
|
||||||
flutter_web_plugins:
|
flutter_web_plugins:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -571,10 +651,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: "9c96da072b421e98183f9ea7464898428e764bc0ce5567f27ec8693442e72514"
|
sha256: bca87b0165ffd7cdb9cad8edd22d18d2201e886d9a9f19b4fb3452ea7df3a72a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.5"
|
version: "2.2.6"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -679,6 +759,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
|
pointer_interceptor:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pointer_interceptor
|
||||||
|
sha256: d0a8e660d1204eaec5bd34b34cc92174690e076d2e4f893d9d68c486a13b07c4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.1+1"
|
||||||
|
pointer_interceptor_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pointer_interceptor_ios
|
||||||
|
sha256: a6906772b3205b42c44614fcea28f818b1e5fdad73a4ca742a7bd49818d9c917
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.1"
|
||||||
|
pointer_interceptor_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pointer_interceptor_platform_interface
|
||||||
|
sha256: "0597b0560e14354baeb23f8375cd612e8bd4841bf8306ecb71fcd0bb78552506"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.0+1"
|
||||||
|
pointer_interceptor_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pointer_interceptor_web
|
||||||
|
sha256: a6237528b46c411d8d55cdfad8fcb3269fc4cbb26060b14bff94879165887d1e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.2"
|
||||||
provider:
|
provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@@ -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: 1.1.11+2268
|
version: 1.1.12+2269
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
@@ -80,6 +80,7 @@ dependencies:
|
|||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
markdown: any
|
markdown: any
|
||||||
|
flutter_typeahead: ^5.2.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
Reference in New Issue
Block a user