mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-16 14:46:44 +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 = [
|
||||
GeneratedFormTextField('github-creds',
|
||||
label: tr('githubPATLabel'),
|
||||
password: true,
|
||||
required: false,
|
||||
additionalValidators: [
|
||||
(value) {
|
||||
|
@ -3,7 +3,6 @@ import 'dart:math';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:obtainium/components/generated_form_modal.dart';
|
||||
import 'package:obtainium/providers/settings_provider.dart';
|
||||
|
||||
abstract class GeneratedFormItem {
|
||||
late String key;
|
||||
@ -24,6 +23,7 @@ class GeneratedFormTextField extends GeneratedFormItem {
|
||||
late bool required;
|
||||
late int max;
|
||||
late String? hint;
|
||||
late bool password;
|
||||
|
||||
GeneratedFormTextField(String key,
|
||||
{String label = 'Input',
|
||||
@ -32,7 +32,8 @@ class GeneratedFormTextField extends GeneratedFormItem {
|
||||
List<String? Function(String? value)> additionalValidators = const [],
|
||||
this.required = true,
|
||||
this.max = 1,
|
||||
this.hint})
|
||||
this.hint,
|
||||
this.password = false})
|
||||
: super(key,
|
||||
label: label,
|
||||
belowWidgets: belowWidgets,
|
||||
@ -129,6 +130,21 @@ class GeneratedForm extends StatefulWidget {
|
||||
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> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
Map<String, dynamic> values = {};
|
||||
@ -153,21 +169,6 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
||||
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
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -188,6 +189,9 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
||||
if (formItem is GeneratedFormTextField) {
|
||||
final formFieldKey = GlobalKey<FormFieldState>();
|
||||
return TextFormField(
|
||||
obscureText: formItem.password,
|
||||
autocorrect: !formItem.password,
|
||||
enableSuggestions: !formItem.password,
|
||||
key: formFieldKey,
|
||||
initialValue: values[formItem.key],
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
|
@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
|
||||
// ignore: implementation_imports
|
||||
import 'package:easy_localization/src/localization.dart';
|
||||
|
||||
const String currentVersion = '0.9.13';
|
||||
const String currentVersion = '0.9.14';
|
||||
const String currentReleaseTag =
|
||||
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
||||
|
||||
|
@ -348,8 +348,9 @@ class AppsPageState extends State<AppsPage> {
|
||||
Row(
|
||||
children: [
|
||||
selectedApps.isEmpty
|
||||
? IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
? TextButton.icon(
|
||||
style:
|
||||
const ButtonStyle(visualDensity: VisualDensity.compact),
|
||||
onPressed: () {
|
||||
selectThese(sortedApps.map((e) => e.app).toList());
|
||||
},
|
||||
@ -357,7 +358,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
Icons.select_all_outlined,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
tooltip: tr('selectAll'))
|
||||
label: Text(sortedApps.length.toString()))
|
||||
: TextButton.icon(
|
||||
style:
|
||||
const ButtonStyle(visualDensity: VisualDensity.compact),
|
||||
@ -375,376 +376,415 @@ class AppsPageState extends State<AppsPage> {
|
||||
label: Text(selectedApps.length.toString())),
|
||||
const VerticalDivider(),
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
selectedApps.isEmpty
|
||||
? const SizedBox()
|
||||
: IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: () {
|
||||
showDialog<Map<String, dynamic>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
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>?>(
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: selectedApps.isEmpty
|
||||
? null
|
||||
: () {
|
||||
showDialog<Map<String, dynamic>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return GeneratedFormModal(
|
||||
title: tr('categorize'),
|
||||
title:
|
||||
tr('removeSelectedAppsQuestion'),
|
||||
items: const [],
|
||||
initValid: true,
|
||||
message:
|
||||
tr('selectedCategorizeWarning'),
|
||||
message: tr(
|
||||
'xWillBeRemovedButRemainInstalled',
|
||||
args: [
|
||||
plural(
|
||||
'apps', selectedApps.length)
|
||||
]),
|
||||
);
|
||||
}) !=
|
||||
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());
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}).then((values) {
|
||||
if (values != null) {
|
||||
appsProvider.removeApps(selectedApps
|
||||
.map((e) => e.id)
|
||||
.toList());
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
showError(err, context);
|
||||
}
|
||||
},
|
||||
tooltip: tr('categorize'),
|
||||
icon: const Icon(Icons.category_outlined),
|
||||
),
|
||||
selectedApps.isEmpty
|
||||
? const SizedBox()
|
||||
: IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: () {
|
||||
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());
|
||||
},
|
||||
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,
|
||||
)),
|
||||
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)
|
||||
.pop();
|
||||
},
|
||||
child: Text(
|
||||
tr('yes')))
|
||||
],
|
||||
);
|
||||
}).whenComplete(() {
|
||||
Navigator.of(
|
||||
context)
|
||||
.pop();
|
||||
});
|
||||
},
|
||||
tooltip:
|
||||
tr('markSelectedAppsUpdated'),
|
||||
icon: const Icon(Icons.done)),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
var pinStatus = selectedApps
|
||||
.where((element) =>
|
||||
element.pinned)
|
||||
.isEmpty;
|
||||
appsProvider.saveApps(
|
||||
selectedApps.map((e) {
|
||||
e.pinned = pinStatus;
|
||||
return e;
|
||||
}).toList());
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
tooltip: selectedApps
|
||||
.where((element) =>
|
||||
element.pinned)
|
||||
.isEmpty
|
||||
? tr('pinToTop')
|
||||
: tr('unpinFromTop'),
|
||||
icon: Icon(selectedApps
|
||||
.where((element) =>
|
||||
element.pinned)
|
||||
.isEmpty
|
||||
? Icons.bookmark_outline_rounded
|
||||
: Icons
|
||||
.bookmark_remove_outlined),
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child:
|
||||
Text(tr('yes')))
|
||||
],
|
||||
);
|
||||
}).whenComplete(() {
|
||||
Navigator.of(
|
||||
context)
|
||||
.pop();
|
||||
});
|
||||
},
|
||||
tooltip: tr(
|
||||
'markSelectedAppsUpdated'),
|
||||
icon: const Icon(
|
||||
Icons.done)),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
var pinStatus =
|
||||
selectedApps
|
||||
.where((element) =>
|
||||
element
|
||||
.pinned)
|
||||
.isEmpty;
|
||||
appsProvider.saveApps(
|
||||
selectedApps.map((e) {
|
||||
e.pinned = pinStatus;
|
||||
return e;
|
||||
}).toList());
|
||||
Navigator.of(context)
|
||||
.pop();
|
||||
},
|
||||
tooltip: selectedApps
|
||||
.where((element) =>
|
||||
element.pinned)
|
||||
.isEmpty
|
||||
? tr('pinToTop')
|
||||
: tr('unpinFromTop'),
|
||||
icon: Icon(selectedApps
|
||||
.where((element) =>
|
||||
element.pinned)
|
||||
.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) {
|
||||
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),
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
tooltip: tr('more'),
|
||||
icon: const Icon(Icons.more_horiz),
|
||||
),
|
||||
],
|
||||
)),
|
||||
);
|
||||
});
|
||||
},
|
||||
tooltip: tr('more'),
|
||||
icon: const Icon(Icons.more_horiz),
|
||||
),
|
||||
],
|
||||
))),
|
||||
const VerticalDivider(),
|
||||
IconButton(
|
||||
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/custom_errors.dart';
|
||||
import 'package:obtainium/providers/apps_provider.dart';
|
||||
import 'package:obtainium/providers/settings_provider.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
@ -28,6 +29,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
Widget build(BuildContext context) {
|
||||
SourceProvider sourceProvider = SourceProvider();
|
||||
var appsProvider = context.read<AppsProvider>();
|
||||
var settingsProvider = context.read<SettingsProvider>();
|
||||
var outlineButtonStyle = ButtonStyle(
|
||||
shape: MaterialStateProperty.all(
|
||||
StadiumBorder(
|
||||
@ -100,6 +102,21 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
appsProvider
|
||||
.importApps(data)
|
||||
.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(
|
||||
tr('importedX', args: [
|
||||
plural('apps', value)
|
||||
|
@ -56,7 +56,7 @@ packages:
|
||||
name: checked_yaml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "2.0.2"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -286,7 +286,7 @@ packages:
|
||||
name: image
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
version: "3.3.0"
|
||||
install_plugin_v2:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -559,7 +559,7 @@ packages:
|
||||
name: shared_preferences_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
version: "2.0.5"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -655,7 +655,7 @@ packages:
|
||||
name: timezone
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.0"
|
||||
version: "0.9.1"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
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: 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:
|
||||
sdk: '>=2.18.2 <3.0.0'
|
||||
|
Reference in New Issue
Block a user