mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-08-01 05:10:15 +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
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl,
|
||||
|
@@ -9,7 +9,7 @@ class FDroidRepo extends AppSource {
|
||||
FDroidRepo() {
|
||||
name = tr('fdroidThirdPartyRepo');
|
||||
canSearch = true;
|
||||
excludeFromMassSearch = true;
|
||||
includeAdditionalOptsInMainSearch = true;
|
||||
neverAutoSelect = 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
|
||||
App endOfGetAppChanges(App app) {
|
||||
var uri = Uri.parse(app.url);
|
||||
@@ -142,6 +163,7 @@ class FDroidRepo extends AppSource {
|
||||
if (appIdOrName == null) {
|
||||
throw NoReleasesError();
|
||||
}
|
||||
additionalSettings['appIdOrName'] = appIdOrName;
|
||||
var res =
|
||||
await sourceRequestWithURLVariants(standardUrl, additionalSettings);
|
||||
if (res.statusCode == 200) {
|
||||
|
@@ -332,10 +332,13 @@ class HTML extends AppSource {
|
||||
additionalSettings['versionExtractWholePage'] == true
|
||||
? versionExtractionWholePageString
|
||||
: relDecoded);
|
||||
version ??=
|
||||
additionalSettings['defaultPseudoVersioningMethod'] == 'APKLinkHash'
|
||||
? rel.hashCode.toString()
|
||||
: (await checkPartialDownloadHashDynamic(rel)).toString();
|
||||
version ??= additionalSettings['defaultPseudoVersioningMethod'] ==
|
||||
'APKLinkHash'
|
||||
? rel.hashCode.toString()
|
||||
: (await checkPartialDownloadHashDynamic(rel,
|
||||
headers: await getRequestHeaders(additionalSettings,
|
||||
forAPKDownload: true)))
|
||||
.toString();
|
||||
return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(),
|
||||
AppNames(uri.host, tr('app')));
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:obtainium/components/generated_form_modal.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||
|
||||
abstract class GeneratedFormItem {
|
||||
late String key;
|
||||
@@ -28,6 +29,7 @@ class GeneratedFormTextField extends GeneratedFormItem {
|
||||
late String? hint;
|
||||
late bool password;
|
||||
late TextInputType? textInputType;
|
||||
late List<String>? autoCompleteOptions;
|
||||
|
||||
GeneratedFormTextField(super.key,
|
||||
{super.label,
|
||||
@@ -39,7 +41,8 @@ class GeneratedFormTextField extends GeneratedFormItem {
|
||||
this.max = 1,
|
||||
this.hint,
|
||||
this.password = false,
|
||||
this.textInputType});
|
||||
this.textInputType,
|
||||
this.autoCompleteOptions});
|
||||
|
||||
@override
|
||||
String ensureType(val) {
|
||||
@@ -274,38 +277,62 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
||||
var formItem = e.value;
|
||||
if (formItem is GeneratedFormTextField) {
|
||||
final formFieldKey = GlobalKey<FormFieldState>();
|
||||
return TextFormField(
|
||||
keyboardType: formItem.textInputType,
|
||||
obscureText: formItem.password,
|
||||
autocorrect: !formItem.password,
|
||||
enableSuggestions: !formItem.password,
|
||||
key: formFieldKey,
|
||||
initialValue: values[formItem.key],
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
onChanged: (value) {
|
||||
var ctrl = TextEditingController(text: values[formItem.key]);
|
||||
return TypeAheadField<String>(
|
||||
controller: ctrl,
|
||||
builder: (context, controller, focusNode) {
|
||||
return TextFormField(
|
||||
controller: ctrl,
|
||||
focusNode: focusNode,
|
||||
keyboardType: formItem.textInputType,
|
||||
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(() {
|
||||
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;
|
||||
suggestionsCallback: (search) {
|
||||
return formItem.autoCompleteOptions
|
||||
?.where((t) => t.toLowerCase().contains(search.toLowerCase()))
|
||||
.toList();
|
||||
},
|
||||
hideOnEmpty: true,
|
||||
);
|
||||
} else if (formItem is GeneratedFormDropdown) {
|
||||
if (formItem.opts!.isEmpty) {
|
||||
|
@@ -51,10 +51,13 @@ class AddAppPageState extends State<AddAppPage> {
|
||||
}
|
||||
|
||||
changeUserInput(String input, bool valid, bool isBuilding,
|
||||
{bool updateUrlInput = false}) {
|
||||
{bool updateUrlInput = false, String? overrideSource}) {
|
||||
userInput = input;
|
||||
if (!isBuilding) {
|
||||
setState(() {
|
||||
if (overrideSource != null) {
|
||||
pickedSourceOverride = overrideSource;
|
||||
}
|
||||
if (updateUrlInput) {
|
||||
urlInputKey++;
|
||||
}
|
||||
@@ -68,6 +71,7 @@ class AddAppPageState extends State<AddAppPage> {
|
||||
if (pickedSource.runtimeType != source.runtimeType ||
|
||||
(prevHost != null && prevHost != source?.hosts[0])) {
|
||||
pickedSource = source;
|
||||
pickedSource?.runOnAddAppInputChange(userInput);
|
||||
additionalSettings = source != null
|
||||
? getDefaultValuesFromFormItems(
|
||||
source.combinedAppSpecificSettingFormItems)
|
||||
@@ -259,9 +263,7 @@ class AddAppPageState extends State<AddAppPage> {
|
||||
searching = true;
|
||||
});
|
||||
var sourceStrings = <String, List<String>>{};
|
||||
sourceProvider.sources
|
||||
.where((e) => e.canSearch && !e.excludeFromMassSearch)
|
||||
.forEach((s) {
|
||||
sourceProvider.sources.where((e) => e.canSearch).forEach((s) {
|
||||
sourceStrings[s.name] = [s.name];
|
||||
});
|
||||
try {
|
||||
@@ -282,32 +284,78 @@ class AddAppPageState extends State<AddAppPage> {
|
||||
settingsProvider.searchDeselected = sourceStrings.keys
|
||||
.where((s) => !searchSources.contains(s))
|
||||
.toList();
|
||||
var results = await Future.wait(sourceProvider.sources
|
||||
.where((e) => searchSources.contains(e.name))
|
||||
.map((e) async {
|
||||
List<MapEntry<String, Map<String, List<String>>>?> results =
|
||||
(await Future.wait(sourceProvider.sources
|
||||
.where((e) => searchSources.contains(e.name))
|
||||
.map((e) async {
|
||||
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) {
|
||||
if (err is! CredsNeededError) {
|
||||
rethrow;
|
||||
} else {
|
||||
err.unexpected = true;
|
||||
showError(err, context);
|
||||
return <String, List<String>>{};
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}));
|
||||
})))
|
||||
.where((a) => a != null)
|
||||
.toList();
|
||||
|
||||
// Interleave results instead of simple reduce
|
||||
Map<String, List<String>> res = {};
|
||||
Map<String, MapEntry<String, List<String>>> res = {};
|
||||
var si = 0;
|
||||
var done = false;
|
||||
while (!done) {
|
||||
done = true;
|
||||
for (var r in results) {
|
||||
if (r.length > si) {
|
||||
var sourceName = r!.key;
|
||||
if (r.value.length > si) {
|
||||
done = false;
|
||||
res.addEntries([r.entries.elementAt(si)]);
|
||||
var singleRes = r.value.entries.elementAt(si);
|
||||
res[singleRes.key] = MapEntry(sourceName, singleRes.value);
|
||||
}
|
||||
}
|
||||
si++;
|
||||
@@ -322,13 +370,15 @@ class AddAppPageState extends State<AddAppPage> {
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return SelectionModal(
|
||||
entries: res,
|
||||
entries: res.map((k, v) => MapEntry(k, v.value)),
|
||||
selectedByDefault: false,
|
||||
onlyOneSelectionAllowed: true,
|
||||
);
|
||||
});
|
||||
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) {
|
||||
@@ -349,7 +399,7 @@ class AddAppPageState extends State<AddAppPage> {
|
||||
[
|
||||
GeneratedFormDropdown(
|
||||
'overrideSource',
|
||||
defaultValue: '',
|
||||
defaultValue: pickedSourceOverride ?? '',
|
||||
[
|
||||
MapEntry('', tr('none')),
|
||||
...sourceProvider.sources.map(
|
||||
|
@@ -161,25 +161,46 @@ class _AppPageState extends State<AppPage> {
|
||||
if (app?.app.apkUrls.isNotEmpty == true ||
|
||||
app?.app.otherAssetUrls.isNotEmpty == true)
|
||||
GestureDetector(
|
||||
onTap: app?.app == null || updating
|
||||
? null
|
||||
: () async {
|
||||
try {
|
||||
await appsProvider
|
||||
.downloadAppAssets([app!.app.id], context);
|
||||
} catch (e) {
|
||||
showError(e, context);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
tr('downloadX', args: [tr('releaseAsset').toLowerCase()]),
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
||||
decoration: TextDecoration.underline,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: app?.app == null || updating
|
||||
? null
|
||||
: () async {
|
||||
try {
|
||||
await appsProvider
|
||||
.downloadAppAssets([app!.app.id], context);
|
||||
} catch (e) {
|
||||
showError(e, context);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
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(
|
||||
height: 48,
|
||||
),
|
||||
|
@@ -466,7 +466,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
hasUpdate ? getUpdateButton(index) : const SizedBox.shrink(),
|
||||
hasUpdate
|
||||
? const SizedBox(
|
||||
width: 10,
|
||||
width: 5,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
GestureDetector(
|
||||
@@ -1105,6 +1105,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
interactive: true,
|
||||
controller: scrollController,
|
||||
child: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: scrollController,
|
||||
slivers: <Widget>[
|
||||
CustomAppBar(title: tr('appsString')),
|
||||
|
@@ -717,7 +717,8 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
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;
|
||||
if (pickAnyAsset) {
|
||||
urlsToSelectFrom = [...urlsToSelectFrom, ...app.otherAssetUrls];
|
||||
@@ -728,7 +729,8 @@ class AppsProvider with ChangeNotifier {
|
||||
// get device supported architecture
|
||||
List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis;
|
||||
|
||||
if (urlsToSelectFrom.length > 1 && context != null) {
|
||||
if ((urlsToSelectFrom.length > 1 || evenIfSingleChoice) &&
|
||||
context != null) {
|
||||
appFileUrl = await showDialog(
|
||||
// ignore: use_build_context_synchronously
|
||||
context: context,
|
||||
@@ -973,7 +975,8 @@ class AppsProvider with ChangeNotifier {
|
||||
if (apps[id]!.app.apkUrls.isNotEmpty ||
|
||||
apps[id]!.app.otherAssetUrls.isNotEmpty) {
|
||||
// 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) {
|
||||
filesToDownload.add(MapEntry(fileUrl, apps[id]!.app));
|
||||
@@ -1650,7 +1653,9 @@ class _AppFilePickerState extends State<AppFilePicker> {
|
||||
? tr('selectX', args: [tr('releaseAsset').toLowerCase()])
|
||||
: tr('pickAnAPK')),
|
||||
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),
|
||||
...urlsToSelectFrom.map(
|
||||
(u) => RadioListTile<String>(
|
||||
|
@@ -354,7 +354,9 @@ preStandardizeUrl(String url) {
|
||||
url.toLowerCase().indexOf('https://') != 0) {
|
||||
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
|
||||
.split('/')
|
||||
.where((e) => e.isNotEmpty)
|
||||
@@ -463,6 +465,10 @@ abstract class AppSource {
|
||||
}
|
||||
}
|
||||
|
||||
void runOnAddAppInputChange(String inputUrl) {
|
||||
//
|
||||
}
|
||||
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
throw NotImplementedError();
|
||||
}
|
||||
@@ -617,7 +623,7 @@ abstract class AppSource {
|
||||
}
|
||||
|
||||
bool canSearch = false;
|
||||
bool excludeFromMassSearch = false;
|
||||
bool includeAdditionalOptsInMainSearch = false;
|
||||
List<GeneratedFormItem> searchQuerySettingFormItems = [];
|
||||
Future<Map<String, List<String>>> search(String query,
|
||||
{Map<String, dynamic> querySettings = const {}}) {
|
||||
|
140
pubspec.lock
140
pubspec.lock
@@ -47,10 +47,34 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: app_links
|
||||
sha256: "96e677810b83707ff5e10fac11e4839daa0ea4e0123c35864c092699165eb3db"
|
||||
sha256: a9905d6a60e814503fabc7523a9ed161b812d7ca69c99ad8ceea14279dc4f06b
|
||||
url: "https://pub.dev"
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -279,18 +303,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flex_color_picker
|
||||
sha256: "31b27677d8d8400e4cff5edb3f189f606dd964d608779b6ae1b7ddad37ea48c6"
|
||||
sha256: "809af4ec82ede3b140ed0219b97d548de99e47aa4b99b14a10f705a2dbbcba5e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.5.0"
|
||||
version: "3.5.1"
|
||||
flex_seed_scheme:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flex_seed_scheme
|
||||
sha256: fb66cdb8ca89084e79efcad2bc2d9deb144666875116f08cdd8d9f8238c8b3ab
|
||||
sha256: "6c595e545b0678e1fe17e8eec3d1fbca7237482da194fadc20ad8607dc7a7f3d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "3.0.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@@ -312,6 +336,54 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -332,10 +404,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_local_notifications
|
||||
sha256: "40e6fbd2da7dcc7ed78432c5cdab1559674b4af035fddbfb2f9a8f9c2112fcef"
|
||||
sha256: ced76d337f54de33d7d9f06092137b4ac2da5079e00cee8a11a1794ffc7c61c6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "17.1.2"
|
||||
version: "17.2.1"
|
||||
flutter_local_notifications_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -348,10 +420,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_platform_interface
|
||||
sha256: "340abf67df238f7f0ef58f4a26d2a83e1ab74c77ab03cd2b2d5018ac64db30b7"
|
||||
sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.1.0"
|
||||
version: "7.2.0"
|
||||
flutter_localizations:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@@ -361,10 +433,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_markdown
|
||||
sha256: ff76a9300a06ad1f2b394e54c0b4beaaf6a95f95c98540c918b870221499bb10
|
||||
sha256: "2e8a801b1ded5ea001a4529c97b1f213dcb11c6b20668e081cafb23468593514"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
version: "0.7.3"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -378,6 +450,14 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
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:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@@ -571,10 +651,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "9c96da072b421e98183f9ea7464898428e764bc0ce5567f27ec8693442e72514"
|
||||
sha256: bca87b0165ffd7cdb9cad8edd22d18d2201e886d9a9f19b4fb3452ea7df3a72a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.5"
|
||||
version: "2.2.6"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -679,6 +759,38 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
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
|
||||
# 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: 1.1.11+2268
|
||||
version: 1.1.12+2269
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
@@ -80,6 +80,7 @@ dependencies:
|
||||
ref: master
|
||||
|
||||
markdown: any
|
||||
flutter_typeahead: ^5.2.0
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
Reference in New Issue
Block a user