Compare commits

...

5 Commits

Author SHA1 Message Date
Imran Remtulla
5c4bb8f84c Merge pull request #217 from ImranR98/dev
Bugfixes and UI Tweaks (#213, #215, #216)
2023-01-06 21:11:58 -05:00
Imran Remtulla
1c8e759494 Increment version + updated packages 2023-01-06 21:11:13 -05:00
Imran Remtulla
081c2a07d2 Categories re-added on import (#213) 2023-01-06 21:10:04 -05:00
Imran Remtulla
02751fe8fa Made GitHub PATs hidden (password field) (#215) 2023-01-06 20:57:26 -05:00
Imran Remtulla
95f3362a84 Apps bottom bar tweaks (#216) 2023-01-06 20:47:22 -05:00
7 changed files with 449 additions and 387 deletions

View File

@@ -15,6 +15,7 @@ class GitHub extends AppSource {
additionalSourceSpecificSettingFormItems = [ additionalSourceSpecificSettingFormItems = [
GeneratedFormTextField('github-creds', GeneratedFormTextField('github-creds',
label: tr('githubPATLabel'), label: tr('githubPATLabel'),
password: true,
required: false, required: false,
additionalValidators: [ additionalValidators: [
(value) { (value) {

View File

@@ -3,7 +3,6 @@ import 'dart:math';
import 'package:easy_localization/easy_localization.dart'; 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/settings_provider.dart';
abstract class GeneratedFormItem { abstract class GeneratedFormItem {
late String key; late String key;
@@ -24,6 +23,7 @@ class GeneratedFormTextField extends GeneratedFormItem {
late bool required; late bool required;
late int max; late int max;
late String? hint; late String? hint;
late bool password;
GeneratedFormTextField(String key, GeneratedFormTextField(String key,
{String label = 'Input', {String label = 'Input',
@@ -32,7 +32,8 @@ class GeneratedFormTextField extends GeneratedFormItem {
List<String? Function(String? value)> additionalValidators = const [], List<String? Function(String? value)> additionalValidators = const [],
this.required = true, this.required = true,
this.max = 1, this.max = 1,
this.hint}) this.hint,
this.password = false})
: super(key, : super(key,
label: label, label: label,
belowWidgets: belowWidgets, belowWidgets: belowWidgets,
@@ -129,6 +130,21 @@ class GeneratedForm extends StatefulWidget {
State<GeneratedForm> createState() => _GeneratedFormState(); State<GeneratedForm> createState() => _GeneratedFormState();
} }
// Generates a random light color
// Courtesy of ChatGPT 😭 (with a bugfix 🥳)
Color generateRandomLightColor() {
// Create a random number generator
final Random random = Random();
// Generate random hue, saturation, and value values
final double hue = random.nextDouble() * 360;
final double saturation = 0.5 + random.nextDouble() * 0.5;
final double value = 0.9 + random.nextDouble() * 0.1;
// Create a HSV color with the random values
return HSVColor.fromAHSV(1.0, hue, saturation, value).toColor();
}
class _GeneratedFormState extends State<GeneratedForm> { class _GeneratedFormState extends State<GeneratedForm> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
Map<String, dynamic> values = {}; Map<String, dynamic> values = {};
@@ -153,21 +169,6 @@ class _GeneratedFormState extends State<GeneratedForm> {
widget.onValueChanges(returnValues, valid, isBuilding); widget.onValueChanges(returnValues, valid, isBuilding);
} }
// Generates a random light color
// Courtesy of ChatGPT 😭 (with a bugfix 🥳)
Color generateRandomLightColor() {
// Create a random number generator
final Random random = Random();
// Generate random hue, saturation, and value values
final double hue = random.nextDouble() * 360;
final double saturation = 0.5 + random.nextDouble() * 0.5;
final double value = 0.9 + random.nextDouble() * 0.1;
// Create a HSV color with the random values
return HSVColor.fromAHSV(1.0, hue, saturation, value).toColor();
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -188,6 +189,9 @@ class _GeneratedFormState extends State<GeneratedForm> {
if (formItem is GeneratedFormTextField) { if (formItem is GeneratedFormTextField) {
final formFieldKey = GlobalKey<FormFieldState>(); final formFieldKey = GlobalKey<FormFieldState>();
return TextFormField( return TextFormField(
obscureText: formItem.password,
autocorrect: !formItem.password,
enableSuggestions: !formItem.password,
key: formFieldKey, key: formFieldKey,
initialValue: values[formItem.key], initialValue: values[formItem.key],
autovalidateMode: AutovalidateMode.onUserInteraction, autovalidateMode: AutovalidateMode.onUserInteraction,

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.9.13'; const String currentVersion = '0.9.14';
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

@@ -348,8 +348,9 @@ class AppsPageState extends State<AppsPage> {
Row( Row(
children: [ children: [
selectedApps.isEmpty selectedApps.isEmpty
? IconButton( ? TextButton.icon(
visualDensity: VisualDensity.compact, style:
const ButtonStyle(visualDensity: VisualDensity.compact),
onPressed: () { onPressed: () {
selectThese(sortedApps.map((e) => e.app).toList()); selectThese(sortedApps.map((e) => e.app).toList());
}, },
@@ -357,7 +358,7 @@ class AppsPageState extends State<AppsPage> {
Icons.select_all_outlined, Icons.select_all_outlined,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
tooltip: tr('selectAll')) label: Text(sortedApps.length.toString()))
: TextButton.icon( : TextButton.icon(
style: style:
const ButtonStyle(visualDensity: VisualDensity.compact), const ButtonStyle(visualDensity: VisualDensity.compact),
@@ -375,376 +376,415 @@ class AppsPageState extends State<AppsPage> {
label: Text(selectedApps.length.toString())), label: Text(selectedApps.length.toString())),
const VerticalDivider(), const VerticalDivider(),
Expanded( Expanded(
child: Row( child: SingleChildScrollView(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, scrollDirection: Axis.horizontal,
children: [ child: Row(
selectedApps.isEmpty mainAxisAlignment: MainAxisAlignment.spaceEvenly,
? const SizedBox() children: [
: IconButton( IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onPressed: () { onPressed: selectedApps.isEmpty
showDialog<Map<String, dynamic>?>( ? null
context: context, : () {
builder: (BuildContext ctx) { showDialog<Map<String, dynamic>?>(
return GeneratedFormModal(
title: tr('removeSelectedAppsQuestion'),
items: const [],
initValid: true,
message: tr(
'xWillBeRemovedButRemainInstalled',
args: [
plural('apps', selectedApps.length)
]),
);
}).then((values) {
if (values != null) {
appsProvider.removeApps(
selectedApps.map((e) => e.id).toList());
}
});
},
tooltip: tr('removeSelectedApps'),
icon: const Icon(Icons.delete_outline_outlined),
),
IconButton(
visualDensity: VisualDensity.compact,
onPressed: appsProvider.areDownloadsRunning() ||
(existingUpdateIdsAllOrSelected.isEmpty &&
newInstallIdsAllOrSelected.isEmpty &&
trackOnlyUpdateIdsAllOrSelected.isEmpty)
? null
: () {
HapticFeedback.heavyImpact();
List<GeneratedFormItem> formItems = [];
if (existingUpdateIdsAllOrSelected.isNotEmpty) {
formItems.add(GeneratedFormSwitch('updates',
label: tr('updateX', args: [
plural('apps',
existingUpdateIdsAllOrSelected.length)
]),
defaultValue: true));
}
if (newInstallIdsAllOrSelected.isNotEmpty) {
formItems.add(GeneratedFormSwitch('installs',
label: tr('installX', args: [
plural('apps',
newInstallIdsAllOrSelected.length)
]),
defaultValue: existingUpdateIdsAllOrSelected
.isNotEmpty));
}
if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) {
formItems.add(GeneratedFormSwitch('trackonlies',
label: tr('markXTrackOnlyAsUpdated', args: [
plural('apps',
trackOnlyUpdateIdsAllOrSelected.length)
]),
defaultValue: existingUpdateIdsAllOrSelected
.isNotEmpty ||
newInstallIdsAllOrSelected.isNotEmpty));
}
showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext ctx) {
var totalApps = existingUpdateIdsAllOrSelected
.length +
newInstallIdsAllOrSelected.length +
trackOnlyUpdateIdsAllOrSelected.length;
return GeneratedFormModal(
title: tr('changeX',
args: [plural('apps', totalApps)]),
items: formItems.map((e) => [e]).toList(),
initValid: true,
);
}).then((values) {
if (values != null) {
if (values.isEmpty) {
values = getDefaultValuesFromFormItems(
[formItems]);
}
bool shouldInstallUpdates =
values['updates'] == true;
bool shouldInstallNew =
values['installs'] == true;
bool shouldMarkTrackOnlies =
values['trackonlies'] == true;
(() async {
if (shouldInstallNew ||
shouldInstallUpdates) {
await settingsProvider
.getInstallPermission();
}
})()
.then((_) {
List<String> toInstall = [];
if (shouldInstallUpdates) {
toInstall
.addAll(existingUpdateIdsAllOrSelected);
}
if (shouldInstallNew) {
toInstall
.addAll(newInstallIdsAllOrSelected);
}
if (shouldMarkTrackOnlies) {
toInstall.addAll(
trackOnlyUpdateIdsAllOrSelected);
}
appsProvider
.downloadAndInstallLatestApps(toInstall,
globalNavigatorKey.currentContext)
.catchError((e) {
showError(e, context);
});
});
}
});
},
tooltip: selectedApps.isEmpty
? tr('installUpdateApps')
: tr('installUpdateSelectedApps'),
icon: const Icon(
Icons.file_download_outlined,
)),
selectedApps.isEmpty
? const SizedBox()
: IconButton(
visualDensity: VisualDensity.compact,
onPressed: () async {
try {
Set<String>? preselected;
var showPrompt = false;
for (var element in selectedApps) {
var currentCats = element.categories.toSet();
if (preselected == null) {
preselected = currentCats;
} else {
if (!settingsProvider.setEqual(
currentCats, preselected)) {
showPrompt = true;
break;
}
}
}
var cont = true;
if (showPrompt) {
cont = await showDialog<Map<String, dynamic>?>(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return GeneratedFormModal( return GeneratedFormModal(
title: tr('categorize'), title:
tr('removeSelectedAppsQuestion'),
items: const [], items: const [],
initValid: true, initValid: true,
message: message: tr(
tr('selectedCategorizeWarning'), 'xWillBeRemovedButRemainInstalled',
args: [
plural(
'apps', selectedApps.length)
]),
); );
}) != }).then((values) {
null; if (values != null) {
} appsProvider.removeApps(selectedApps
if (cont) { .map((e) => e.id)
await showDialog<Map<String, dynamic>?>( .toList());
context: context, }
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: tr('categorize'),
items: const [],
initValid: true,
singleNullReturnButton: tr('continue'),
additionalWidgets: [
CategoryEditorSelector(
preselected: !showPrompt
? preselected ?? {}
: {},
showLabelWhenNotEmpty: false,
onSelected: (categories) {
appsProvider
.saveApps(selectedApps.map((e) {
e.categories = categories;
return e;
}).toList());
},
)
],
);
}); });
} },
} catch (err) { tooltip: tr('removeSelectedApps'),
showError(err, context); icon: const Icon(Icons.delete_outline_outlined),
} ),
}, IconButton(
tooltip: tr('categorize'), visualDensity: VisualDensity.compact,
icon: const Icon(Icons.category_outlined), onPressed: appsProvider.areDownloadsRunning() ||
), (existingUpdateIdsAllOrSelected.isEmpty &&
selectedApps.isEmpty newInstallIdsAllOrSelected.isEmpty &&
? const SizedBox() trackOnlyUpdateIdsAllOrSelected.isEmpty)
: IconButton( ? null
visualDensity: VisualDensity.compact, : () {
onPressed: () { HapticFeedback.heavyImpact();
showDialog( List<GeneratedFormItem> formItems = [];
context: context, if (existingUpdateIdsAllOrSelected
builder: (BuildContext ctx) { .isNotEmpty) {
return AlertDialog( formItems.add(GeneratedFormSwitch(
scrollable: true, 'updates',
content: Padding( label: tr('updateX', args: [
padding: const EdgeInsets.only(top: 6), plural(
child: Row( 'apps',
mainAxisAlignment: existingUpdateIdsAllOrSelected
MainAxisAlignment.spaceAround, .length)
children: [ ]),
IconButton( defaultValue: true));
onPressed: }
appsProvider if (newInstallIdsAllOrSelected.isNotEmpty) {
.areDownloadsRunning() formItems.add(GeneratedFormSwitch(
? null 'installs',
: () { label: tr('installX', args: [
showDialog( plural(
context: context, 'apps',
builder: newInstallIdsAllOrSelected
(BuildContext .length)
ctx) { ]),
return AlertDialog( defaultValue:
title: Text(tr( existingUpdateIdsAllOrSelected
'markXSelectedAppsAsUpdated', .isNotEmpty));
args: [ }
selectedApps if (trackOnlyUpdateIdsAllOrSelected
.length .isNotEmpty) {
.toString() formItems.add(GeneratedFormSwitch(
])), 'trackonlies',
content: Text( label: tr('markXTrackOnlyAsUpdated',
tr('onlyWorksWithNonEVDApps'), args: [
style: const TextStyle( plural(
fontWeight: 'apps',
FontWeight trackOnlyUpdateIdsAllOrSelected
.bold, .length)
fontStyle: ]),
FontStyle.italic), defaultValue:
), existingUpdateIdsAllOrSelected
actions: [ .isNotEmpty ||
TextButton( newInstallIdsAllOrSelected
onPressed: .isNotEmpty));
() { }
Navigator.of(context) showDialog<Map<String, dynamic>?>(
.pop(); context: context,
}, builder: (BuildContext ctx) {
child: Text( var totalApps =
tr('no'))), existingUpdateIdsAllOrSelected.length +
TextButton( newInstallIdsAllOrSelected
onPressed: .length +
() { trackOnlyUpdateIdsAllOrSelected
HapticFeedback .length;
.selectionClick(); return GeneratedFormModal(
appsProvider title: tr('changeX', args: [
.saveApps(selectedApps.map((a) { plural('apps', totalApps)
if (a.installedVersion != ]),
null) { items: formItems
a.installedVersion = a.latestVersion; .map((e) => [e])
} .toList(),
return a; initValid: true,
}).toList()); );
}).then((values) {
if (values != null) {
if (values.isEmpty) {
values =
getDefaultValuesFromFormItems(
[formItems]);
}
bool shouldInstallUpdates =
values['updates'] == true;
bool shouldInstallNew =
values['installs'] == true;
bool shouldMarkTrackOnlies =
values['trackonlies'] == true;
(() async {
if (shouldInstallNew ||
shouldInstallUpdates) {
await settingsProvider
.getInstallPermission();
}
})()
.then((_) {
List<String> toInstall = [];
if (shouldInstallUpdates) {
toInstall.addAll(
existingUpdateIdsAllOrSelected);
}
if (shouldInstallNew) {
toInstall.addAll(
newInstallIdsAllOrSelected);
}
if (shouldMarkTrackOnlies) {
toInstall.addAll(
trackOnlyUpdateIdsAllOrSelected);
}
appsProvider
.downloadAndInstallLatestApps(
toInstall,
globalNavigatorKey
.currentContext)
.catchError((e) {
showError(e, context);
});
});
}
});
},
tooltip: selectedApps.isEmpty
? tr('installUpdateApps')
: tr('installUpdateSelectedApps'),
icon: const Icon(
Icons.file_download_outlined,
)),
IconButton(
visualDensity: VisualDensity.compact,
onPressed: selectedApps.isEmpty
? null
: () async {
try {
Set<String>? preselected;
var showPrompt = false;
for (var element in selectedApps) {
var currentCats =
element.categories.toSet();
if (preselected == null) {
preselected = currentCats;
} else {
if (!settingsProvider.setEqual(
currentCats, preselected)) {
showPrompt = true;
break;
}
}
}
var cont = true;
if (showPrompt) {
cont = await showDialog<
Map<String, dynamic>?>(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: tr('categorize'),
items: const [],
initValid: true,
message: tr(
'selectedCategorizeWarning'),
);
}) !=
null;
}
if (cont) {
await showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: tr('categorize'),
items: const [],
initValid: true,
singleNullReturnButton:
tr('continue'),
additionalWidgets: [
CategoryEditorSelector(
preselected: !showPrompt
? preselected ?? {}
: {},
showLabelWhenNotEmpty: false,
onSelected: (categories) {
appsProvider.saveApps(
selectedApps.map((e) {
e.categories = categories;
return e;
}).toList());
},
)
],
);
});
}
} catch (err) {
showError(err, context);
}
},
tooltip: tr('categorize'),
icon: const Icon(Icons.category_outlined),
),
IconButton(
visualDensity: VisualDensity.compact,
onPressed: selectedApps.isEmpty
? null
: () {
showDialog(
context: context,
builder: (BuildContext ctx) {
return AlertDialog(
scrollable: true,
content: Padding(
padding:
const EdgeInsets.only(top: 6),
child: Row(
mainAxisAlignment:
MainAxisAlignment
.spaceAround,
children: [
IconButton(
onPressed: appsProvider
.areDownloadsRunning()
? null
: () {
showDialog(
context:
context,
builder:
(BuildContext
ctx) {
return AlertDialog(
title: Text(tr(
'markXSelectedAppsAsUpdated',
args: [
selectedApps.length.toString()
])),
content:
Text(
tr('onlyWorksWithNonEVDApps'),
style: const TextStyle(
fontWeight:
FontWeight.bold,
fontStyle: FontStyle.italic),
),
actions: [
TextButton(
onPressed:
() {
Navigator.of(context).pop();
},
child:
Text(tr('no'))),
TextButton(
onPressed:
() {
HapticFeedback.selectionClick();
appsProvider.saveApps(selectedApps.map((a) {
if (a.installedVersion != null) {
a.installedVersion = a.latestVersion;
}
return a;
}).toList());
Navigator.of(context) Navigator.of(context).pop();
.pop(); },
}, child:
child: Text( Text(tr('yes')))
tr('yes'))) ],
], );
); }).whenComplete(() {
}).whenComplete(() { Navigator.of(
Navigator.of( context)
context) .pop();
.pop(); });
}); },
}, tooltip: tr(
tooltip: 'markSelectedAppsUpdated'),
tr('markSelectedAppsUpdated'), icon: const Icon(
icon: const Icon(Icons.done)), Icons.done)),
IconButton( IconButton(
onPressed: () { onPressed: () {
var pinStatus = selectedApps var pinStatus =
.where((element) => selectedApps
element.pinned) .where((element) =>
.isEmpty; element
appsProvider.saveApps( .pinned)
selectedApps.map((e) { .isEmpty;
e.pinned = pinStatus; appsProvider.saveApps(
return e; selectedApps.map((e) {
}).toList()); e.pinned = pinStatus;
Navigator.of(context).pop(); return e;
}, }).toList());
tooltip: selectedApps Navigator.of(context)
.where((element) => .pop();
element.pinned) },
.isEmpty tooltip: selectedApps
? tr('pinToTop') .where((element) =>
: tr('unpinFromTop'), element.pinned)
icon: Icon(selectedApps .isEmpty
.where((element) => ? tr('pinToTop')
element.pinned) : tr('unpinFromTop'),
.isEmpty icon: Icon(selectedApps
? Icons.bookmark_outline_rounded .where((element) =>
: Icons element.pinned)
.bookmark_remove_outlined), .isEmpty
? Icons
.bookmark_outline_rounded
: Icons
.bookmark_remove_outlined),
),
IconButton(
onPressed: () {
String urls = '';
for (var a
in selectedApps) {
urls += '${a.url}\n';
}
urls = urls.substring(
0, urls.length - 1);
Share.share(urls,
subject: tr(
'selectedAppURLsFromObtainium'));
Navigator.of(context)
.pop();
},
tooltip: tr(
'shareSelectedAppURLs'),
icon:
const Icon(Icons.share),
),
IconButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext
ctx) {
return GeneratedFormModal(
title: tr(
'resetInstallStatusForSelectedAppsQuestion'),
items: const [],
initValid: true,
message: tr(
'installStatusOfXWillBeResetExplanation',
args: [
plural(
'app',
selectedApps
.length)
]),
);
}).then((values) {
if (values != null) {
appsProvider.saveApps(
selectedApps
.map((e) {
e.installedVersion =
null;
return e;
}).toList());
}
}).whenComplete(() {
Navigator.of(context)
.pop();
});
},
tooltip: tr(
'resetInstallStatus'),
icon: const Icon(Icons
.restore_page_outlined),
),
]),
), ),
IconButton( );
onPressed: () { });
String urls = ''; },
for (var a in selectedApps) { tooltip: tr('more'),
urls += '${a.url}\n'; icon: const Icon(Icons.more_horiz),
} ),
urls = urls.substring( ],
0, urls.length - 1); ))),
Share.share(urls,
subject: tr(
'selectedAppURLsFromObtainium'));
Navigator.of(context).pop();
},
tooltip: tr('shareSelectedAppURLs'),
icon: const Icon(Icons.share),
),
IconButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: tr(
'resetInstallStatusForSelectedAppsQuestion'),
items: const [],
initValid: true,
message: tr(
'installStatusOfXWillBeResetExplanation',
args: [
plural(
'app',
selectedApps
.length)
]),
);
}).then((values) {
if (values != null) {
appsProvider.saveApps(
selectedApps.map((e) {
e.installedVersion = null;
return e;
}).toList());
}
}).whenComplete(() {
Navigator.of(context).pop();
});
},
tooltip: tr('resetInstallStatus'),
icon: const Icon(
Icons.restore_page_outlined),
),
]),
),
);
});
},
tooltip: tr('more'),
icon: const Icon(Icons.more_horiz),
),
],
)),
const VerticalDivider(), const VerticalDivider(),
IconButton( IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,

View File

@@ -9,6 +9,7 @@ import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/components/generated_form_modal.dart'; import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/settings_provider.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
@@ -28,6 +29,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
SourceProvider sourceProvider = SourceProvider(); SourceProvider sourceProvider = SourceProvider();
var appsProvider = context.read<AppsProvider>(); var appsProvider = context.read<AppsProvider>();
var settingsProvider = context.read<SettingsProvider>();
var outlineButtonStyle = ButtonStyle( var outlineButtonStyle = ButtonStyle(
shape: MaterialStateProperty.all( shape: MaterialStateProperty.all(
StadiumBorder( StadiumBorder(
@@ -100,6 +102,21 @@ class _ImportExportPageState extends State<ImportExportPage> {
appsProvider appsProvider
.importApps(data) .importApps(data)
.then((value) { .then((value) {
var cats =
settingsProvider.categories;
appsProvider.apps
.forEach((key, value) {
for (var c
in value.app.categories) {
if (!cats.containsKey(c)) {
cats[c] =
generateRandomLightColor()
.value;
}
}
});
settingsProvider.categories =
cats;
showError( showError(
tr('importedX', args: [ tr('importedX', args: [
plural('apps', value) plural('apps', value)

View File

@@ -56,7 +56,7 @@ packages:
name: checked_yaml name: checked_yaml
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.2"
cli_util: cli_util:
dependency: transitive dependency: transitive
description: description:
@@ -286,7 +286,7 @@ packages:
name: image name: image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.2.2" version: "3.3.0"
install_plugin_v2: install_plugin_v2:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -559,7 +559,7 @@ packages:
name: shared_preferences_macos name: shared_preferences_macos
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.4" version: "2.0.5"
shared_preferences_platform_interface: shared_preferences_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -655,7 +655,7 @@ packages:
name: timezone name: timezone
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.9.0" version: "0.9.1"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: 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 # 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.9.13+103 # When changing this, update the tag in main() accordingly version: 0.9.14+104 # 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'