mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-21 08:39:42 +02:00
Compare commits
5 Commits
v0.9.13-be
...
v0.9.14-be
Author | SHA1 | Date | |
---|---|---|---|
|
5c4bb8f84c | ||
|
1c8e759494 | ||
|
081c2a07d2 | ||
|
02751fe8fa | ||
|
95f3362a84 |
@@ -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) {
|
||||||
|
@@ -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,
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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,
|
||||||
|
@@ -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)
|
||||||
|
@@ -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:
|
||||||
|
@@ -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'
|
||||||
|
Reference in New Issue
Block a user