Files
lunasea/lib/modules/settings/core/dialogs.dart
Jagandeep Brar 291db7321f [Release] v3.3.0+330001 (#235)
NEW
- [Home/Calendar] New "schedule" view to view all days as one list
- [Home/Modules] Option to use module "brand" colours or the LunaSea list colours
- [Search] Option to hide adult categories
- [-arr/Catalogue] Ability to sort by date added
- [-arr/Search] Show the indexer/tracker on the collapsed tile

TWEAKS
- [Development] Updated Flutter packages

FIXES
- Multiple smaller crash/error fixes

Thanks to @lightglitch on GitHub for implementing the schedule calendar mode!
2020-08-16 14:01:11 -05:00

901 lines
34 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/home/core.dart';
import 'package:wake_on_lan/wake_on_lan.dart';
class SettingsDialogs {
SettingsDialogs._();
static Future<List<dynamic>> editHost(BuildContext context, String title, { String prefill = '' }) async {
bool _flag = false;
final _formKey = GlobalKey<FormState>();
final _textController = TextEditingController()..text = prefill;
void _setValues(bool flag) {
if(_formKey.currentState.validate()) {
_flag = flag;
Navigator.of(context).pop();
}
}
await LSDialog.dialog(
context: context,
title: title,
buttons: [
LSDialog.button(
text: 'Save',
onPressed: () => _setValues(true),
),
],
content: [
LSDialog.richText(
children: [
LSDialog.textSpanContent(text: '\tThis is the URL in which you access the web GUI for the service\n'),
LSDialog.textSpanContent(text: '\tYou must include either '),
LSDialog.bolded(text: 'http://'),
LSDialog.textSpanContent(text: ' or '),
LSDialog.bolded(text: 'https://\n'),
LSDialog.textSpanContent(text: '\tWhen not using a reverse proxy, please include the port: '),
LSDialog.bolded(text: 'url:port\n'),
LSDialog.textSpanContent(text: '\tTo add basic authentication, please set a custom header in the advanced section'),
],
),
Form(
key: _formKey,
child: LSDialog.textFormInput(
controller: _textController,
title: title,
keyboardType: TextInputType.url,
onSubmitted: (_) => _setValues(true),
validator: (_) => null,
),
),
],
contentPadding: LSDialog.inputTextDialogContentPadding(),
);
return [_flag, _textController.text];
}
static Future<List<dynamic>> deleteIndexer(BuildContext context) async {
bool _flag = false;
void _setValues(bool flag) {
_flag = flag;
Navigator.of(context).pop();
}
await LSDialog.dialog(
context: context,
title: 'Delete Indexer',
buttons: [
LSDialog.button(
text: 'Delete',
textColor: Colors.red,
onPressed: () => _setValues(true),
),
],
content: [
LSDialog.textContent(
text: 'Are you sure you want to delete this indexer?'
),
],
contentPadding: LSDialog.textDialogContentPadding(),
);
return [_flag];
}
static Future<List<dynamic>> deleteHeader(BuildContext context) async {
bool _flag = false;
void _setValues(bool flag) {
_flag = flag;
Navigator.of(context).pop();
}
await LSDialog.dialog(
context: context,
title: 'Delete Header',
buttons: [
LSDialog.button(
text: 'Delete',
textColor: Colors.red,
onPressed: () => _setValues(true),
),
],
content: [
LSDialog.textContent(
text: 'Are you sure you want to delete this header?'
),
],
contentPadding: LSDialog.textDialogContentPadding(),
);
return [_flag];
}
static Future<List<dynamic>> addHeader(BuildContext context) async {
List<List<dynamic>> _options = [
['Basic Authentication', Icons.verified_user, 1],
['Custom...', Icons.device_hub, 100],
];
bool _flag = false;
int _type = -1;
void _setValues(bool flag, int type) {
_flag = flag;
_type = type;
Navigator.of(context).pop();
}
await LSDialog.dialog(
context: context,
title: 'Add Header',
content: List.generate(
_options.length,
(index) => LSDialog.tile(
text: _options[index][0],
icon: _options[index][1],
iconColor: LSColors.list(index),
onTap: () => _setValues(true, _options[index][2]),
),
),
contentPadding: LSDialog.listDialogContentPadding(),
);
return [_flag, _type];
}
static Future<List<dynamic>> addCustomHeader(BuildContext context) async {
bool _flag = false;
final formKey = GlobalKey<FormState>();
TextEditingController _key = TextEditingController();
TextEditingController _value = TextEditingController();
void _setValues(bool flag) {
if(formKey.currentState.validate()) {
_flag = flag;
Navigator.of(context).pop();
}
}
await LSDialog.dialog(
context: context,
title: 'Custom Header',
buttons: [
LSDialog.button(
text: 'Add',
onPressed: () => _setValues(true),
),
],
content: [
Form(
key: formKey,
child: Column(
children: [
LSDialog.textFormInput(
controller: _key,
validator: (key) => key.length > 0 ? null : 'Key Required',
onSubmitted: (_) => _setValues(true),
title: 'Header Key',
),
LSDialog.textFormInput(
controller: _value,
validator: (value) => value.length > 0 ? null : 'Value Required',
onSubmitted: (_) => _setValues(true),
title: 'Header Value',
),
],
),
),
],
contentPadding: LSDialog.inputDialogContentPadding(),
);
return [_flag, _key.text, _value.text];
}
static Future<List<dynamic>> addAuthenticationHeader(BuildContext context) async {
bool _flag = false;
final _formKey = GlobalKey<FormState>();
final _username = TextEditingController();
final _password = TextEditingController();
void _setValues(bool flag) {
if(_formKey.currentState.validate()) {
_flag = flag;
Navigator.of(context).pop();
}
}
await LSDialog.dialog(
context: context,
title: 'Basic Authentication',
buttons: [
LSDialog.button(
text: 'Add',
onPressed: () => _setValues(true),
),
],
content: [
LSDialog.richText(
children: [
LSDialog.textSpanContent(text: '\tThe username '),
LSDialog.bolded(text: 'cannot'),
LSDialog.textSpanContent(text: ' contain a colon\n'),
LSDialog.textSpanContent(text: '\tThe password '),
LSDialog.bolded(text: 'can'),
LSDialog.textSpanContent(text: ' contain a colon\n'),
LSDialog.textSpanContent(text: '\tThe username and password are automatically converted to base64 encoding\n'),
]
),
Form(
key: _formKey,
child: Column(
children: [
LSDialog.textFormInput(
controller: _username,
validator: (username) => username.length > 0 ? null : 'Username Required',
onSubmitted: (_) => _setValues(true),
title: 'Username',
),
LSDialog.textFormInput(
controller: _password,
validator: (password) => password.length > 0 ? null : 'Password Required',
onSubmitted: (_) => _setValues(true),
obscureText: true,
title: 'Password',
),
],
),
),
],
contentPadding: LSDialog.inputTextDialogContentPadding(),
);
return [_flag, _username.text, _password.text];
}
static Future<List<dynamic>> exportLogs(BuildContext context) async {
bool _flag = false;
void _setValues(bool flag) {
_flag = flag;
Navigator.of(context).pop();
}
await LSDialog.dialog(
context: context,
title: 'Export Logs',
buttons: [
LSDialog.button(
text: 'Export',
onPressed: () => _setValues(true),
textColor: LSColors.accent,
),
],
content: [
LSDialog.textContent(text: 'Are you sure you want to export all recorded logs to the filesystem?\n'),
LSDialog.richText(
children: [
if(Platform.isIOS || Platform.isAndroid) LSDialog.textSpanContent(text: 'The exported logs can be found in '),
if(Platform.isIOS) LSDialog.bolded(text: '<On My Device>/LunaSea/FLogs'),
if(Platform.isAndroid) LSDialog.bolded(text: '<Storage>/Android/data/app.lunasea.lunasea/files/FLogs'),
LSDialog.textSpanContent(text: '.'),
],
alignment: TextAlign.center,
),
],
contentPadding: LSDialog.textDialogContentPadding(),
);
return [_flag];
}
static Future<List<dynamic>> clearLogs(BuildContext context) async {
bool _flag = false;
void _setValues(bool flag) {
_flag = flag;
Navigator.of(context).pop();
}
await LSDialog.dialog(
context: context,
title: 'Clear Logs',
buttons: [
LSDialog.button(
text: 'Clear',
onPressed: () => _setValues(true),
textColor: LSColors.red,
),
],
content: [
LSDialog.textContent(text: 'Are you sure you want to clear all recorded logs?\n'),
LSDialog.textContent(text: 'Logs can be useful for bug reports and debugging.'),
],
contentPadding: LSDialog.textDialogContentPadding(),
);
return [_flag];
}
static Future<List<dynamic>> toggleStrictTLS(BuildContext context) async {
bool _flag = false;
void _setValues(bool flag) {
_flag = flag;
Navigator.of(context).pop();
}
await LSDialog.dialog(
context: context,
title: 'Disable Strict SSL/TLS Validation',
buttons: [
LSDialog.button(
text: 'Disable',
onPressed: () => _setValues(true),
textColor: LSColors.red,
),
],
content: [
LSDialog.richText(
children: [
LSDialog.bolded(
text: 'Please do not modify this setting unless you know what you are doing.\n\n',
color: LSColors.red,
fontSize: LSDialog.SUBBODY_SIZE,
),
LSDialog.textSpanContent(text: 'Are you sure you want to disable strict SSL/TLS validation?\n\n'),
LSDialog.textSpanContent(text: 'Disabling strict SSL/TLS validation means that LunaSea will not validate the host machine\'s SSL certificate against a certificate authority.\n\n'),
LSDialog.textSpanContent(text: 'LunaSea will still connect to your host machine securely when using SSL/TLS whether strict SSL/TLS validation is enabled or disabled.\n\n'),
LSDialog.bolded(
text: 'Warning: Disabling strict SSL/TLS for an invalid or self-signed certificate will prevent a large amount of images from loading within LunaSea.',
color: LSColors.red,
fontSize: LSDialog.SUBBODY_SIZE,
),
],
alignment: TextAlign.center,
),
],
contentPadding: LSDialog.textDialogContentPadding(),
);
return [_flag];
}
static Future<List<dynamic>> nzbgetBasicAuthentication(BuildContext context) async {
bool _flag = false;
void _setValues(bool flag) {
_flag = flag;
Navigator.of(context).pop();
}
await LSDialog.dialog(
context: context,
title: 'Use Basic Authentication',
buttons: [
LSDialog.button(
text: 'Use',
onPressed: () => _setValues(true),
textColor: LSColors.red,
),
],
content: [
LSDialog.richText(
children: [
LSDialog.bolded(
text: 'Please do not modify this setting unless you know what you are doing.\n\n',
color: LSColors.red,
fontSize: LSDialog.SUBBODY_SIZE,
),
LSDialog.textSpanContent(text: 'Are you sure you want to use basic authentication to connect to NZBGet?\n\n'),
LSDialog.textSpanContent(text: 'Basic authentication will add your username and password as a header in the request instead of encoding the details into the URL.\n\n'),
LSDialog.bolded(
text: 'Warning: This will allow you to have more complex passwords, but interfere with layered authentication methods.',
color: LSColors.red,
fontSize: LSDialog.SUBBODY_SIZE,
),
],
alignment: TextAlign.center,
),
],
contentPadding: LSDialog.textDialogContentPadding(),
);
return [_flag];
}
static Future<List<dynamic>> addProfile(BuildContext context) async {
final _formKey = GlobalKey<FormState>();
final _controller = TextEditingController();
bool _flag = false;
void _setValues(bool flag) {
if(_formKey.currentState.validate()) {
_flag = flag;
Navigator.of(context).pop();
}
}
await LSDialog.dialog(
context: context,
title: 'Add Profile',
buttons: [
LSDialog.button(
text: 'Save',
onPressed: () => _setValues(true),
),
],
content: [
Form(
key: _formKey,
child: LSDialog.textFormInput(
controller: _controller,
validator: (value) => value.length > 0 ? null : 'Name Required',
onSubmitted: (_) => _setValues(true),
title: 'Profile Name',
),
),
],
contentPadding: LSDialog.inputDialogContentPadding(),
);
return [_flag, _controller.text];
}
static Future<List<dynamic>> renameProfile(BuildContext context, List<String> profiles) async {
bool _flag = false;
String _profile = '';
void _setValues(bool flag, String profile) {
_flag = flag;
_profile = profile;
Navigator.of(context).pop();
}
await LSDialog.dialog(
context: context,
title: 'Rename Profile',
content: List.generate(
profiles.length,
(index) => LSDialog.tile(
icon: Icons.settings,
iconColor: LSColors.list(index),
text: profiles[index],
onTap: () => _setValues(true, profiles[index]),
),
),
contentPadding: LSDialog.listDialogContentPadding(),
);
return [_flag, _profile];
}
static Future<List<dynamic>> renameProfileSelected(BuildContext context) async {
final _formKey = GlobalKey<FormState>();
final _controller = TextEditingController();
bool _flag = false;
void _setValues(bool flag) {
if(_formKey.currentState.validate()) {
_flag = flag;
Navigator.of(context).pop();
}
}
await LSDialog.dialog(
context: context,
title: 'Rename Profile',
buttons: [
LSDialog.button(text: 'Rename', onPressed: () => _setValues(true), textColor: LSColors.accent),
],
content: [
Form(
key: _formKey,
child:LSDialog.textFormInput(
controller: _controller,
validator: (value) => value.length > 0 ? null : 'Name Required',
onSubmitted: (_) => _setValues(true),
title: 'New Profile Name',
),
),
],
contentPadding: LSDialog.inputDialogContentPadding(),
);
return [_flag, _controller.text];
}
static Future<List<dynamic>> deleteProfile(BuildContext context, List<String> profiles) async {
bool _flag = false;
String _profile = '';
void _setValues(bool flag, String profile) {
_flag = flag;
_profile = profile;
Navigator.of(context).pop();
}
await LSDialog.dialog(
context: context,
title: 'Delete Profile',
content: List.generate(
profiles.length,
(index) => LSDialog.tile(
icon: Icons.settings,
iconColor: LSColors.list(index),
text: profiles[index],
onTap: () => _setValues(true, profiles[index]),
),
),
contentPadding: LSDialog.listDialogContentPadding(),
);
return [_flag, _profile];
}
static Future<List<dynamic>> enabledProfile(BuildContext context, List<String> profiles) async {
bool _flag = false;
String _profile = '';
void _setValues(bool flag, String profile) {
_flag = flag;
_profile = profile;
Navigator.of(context).pop();
}
await LSDialog.dialog(
context: context,
title: 'Enabled Profile',
content: List.generate(
profiles.length,
(index) => LSDialog.tile(
icon: Icons.settings,
iconColor: LSColors.list(index),
text: profiles[index],
onTap: () => _setValues(true, profiles[index]),
),
),
contentPadding: LSDialog.listDialogContentPadding(),
);
return [_flag, _profile];
}
static Future<List<dynamic>> changeBrowser(BuildContext context) async {
bool _flag = false;
LSBrowsers _browser;
void _setValues(bool flag, LSBrowsers browser) {
_flag = flag;
_browser = browser;
Navigator.of(context).pop();
}
await LSDialog.dialog(
context: context,
title: 'Open Links In...',
content: List.generate(
LSBrowsers.values.length,
(index) => LSDialog.tile(
icon: LSBrowsers.values[index].icon,
iconColor: LSColors.list(index),
text: LSBrowsers.values[index].name,
onTap: () => _setValues(true, LSBrowsers.values[index]),
),
),
contentPadding: LSDialog.listDialogContentPadding(),
);
return [_flag, _browser];
}
static Future<List<dynamic>> editCalendarStartingDay(BuildContext context) async {
bool _flag = false;
CalendarStartingDay _startingDate;
void _setValues(bool flag, CalendarStartingDay startingDate) {
_flag = flag;
_startingDate = startingDate;
Navigator.of(context).pop();
}
await LSDialog.dialog(
context: context,
title: 'Starting Day of Week',
content: List.generate(
CalendarStartingDay.values.length,
(index) => LSDialog.tile(
icon: CustomIcons.calendar,
iconColor: LSColors.list(index),
text: CalendarStartingDay.values[index].name,
onTap: () => _setValues(true, CalendarStartingDay.values[index]),
),
),
contentPadding: LSDialog.listDialogContentPadding(),
);
return [_flag, _startingDate];
}
static Future<List<dynamic>> editCalendarStartingSize(BuildContext context) async {
bool _flag = false;
CalendarStartingSize _startingSize;
void _setValues(bool flag, CalendarStartingSize startingSize) {
_flag = flag;
_startingSize = startingSize;
Provider.of<HomeModel>(context, listen: false).showCalendarSchedule = _startingSize == CalendarStartingSize.SCHEDULE;
Navigator.of(context).pop();
}
await LSDialog.dialog(
context: context,
title: 'Starting Size',
content: List.generate(
CalendarStartingSize.values.length,
(index) => LSDialog.tile(
icon: CalendarStartingSize.values[index].icon,
iconColor: LSColors.list(index),
text: CalendarStartingSize.values[index].name,
onTap: () => _setValues(true, CalendarStartingSize.values[index]),
),
),
contentPadding: LSDialog.listDialogContentPadding(),
);
return [_flag, _startingSize];
}
static Future<List<dynamic>> editBroadcastAddress(BuildContext context, String prefill) async {
bool _flag = false;
final _formKey = GlobalKey<FormState>();
final _controller = TextEditingController()..text = prefill;
void _setValues(bool flag) {
if(_formKey.currentState.validate()) {
_flag = flag;
Navigator.of(context).pop();
}
}
await LSDialog.dialog(
context: context,
title: 'Broadcast Address',
buttons: [
LSDialog.button(
text: 'Save',
onPressed: () => _setValues(true),
),
],
content: [
LSDialog.richText(
children: [
LSDialog.textSpanContent(text: '\tThis is the broadcast address of your local network\n'),
LSDialog.textSpanContent(text: '\tTypically this is the IP address of your machine with the last octet set to 255\n'),
LSDialog.textSpanContent(text: '\tGiven an example machine IP address of 192.168.1.111, the resulting broadcast IP address is '),
LSDialog.bolded(text: '192.168.1.255'),
],
),
Form(
key: _formKey,
child: LSDialog.textFormInput(
controller: _controller,
validator: (address) => IPv4Address.validate(address)
? null
: 'Invalid Broadcast Address',
onSubmitted: (_) => _setValues(true),
title: 'Broadcast Address',
),
),
],
contentPadding: LSDialog.inputTextDialogContentPadding(),
);
return [_flag, _controller.text];
}
static Future<List<dynamic>> editMACAddress(BuildContext context, String prefill) async {
bool _flag = false;
final formKey = GlobalKey<FormState>();
final _controller = TextEditingController()..text = prefill;
void _setValues(bool flag) {
if(formKey.currentState.validate()) {
_flag = flag;
Navigator.of(context).pop();
}
}
await LSDialog.dialog(
context: context,
title: 'MAC Address',
buttons: [
LSDialog.button(
text: 'Save',
onPressed: () => _setValues(true),
),
],
content: [
LSDialog.richText(
children: [
LSDialog.textSpanContent(text: '\tThis is the MAC address of the machine that you want to wake up\n'),
LSDialog.textSpanContent(text: '\tMAC addresses contain six two-digit hexidecimal nibbles (an octet)\n'),
LSDialog.textSpanContent(text: '\tHexidecimal digits range from 0-9 and A-F\n'),
LSDialog.textSpanContent(text: '\tEach hexidecimal octet should be separated by a colon\n'),
LSDialog.textSpanContent(text: '\tExample: '),
LSDialog.bolded(text: 'A4:83:E7:0D:7F:4F'),
],
),
Form(
key: formKey,
child: LSDialog.textFormInput(
controller: _controller,
validator: (address) => MACAddress.validate(address)
? null
: 'Invalid MAC Address',
onSubmitted: (_) => _setValues(true),
title: 'MAC Address',
),
),
],
contentPadding: LSDialog.inputTextDialogContentPadding(),
);
return [_flag, _controller.text];
}
static Future<List<dynamic>> clearConfiguration(BuildContext context) async {
bool _flag = false;
void _setValues(bool flag) {
_flag = flag;
Navigator.of(context).pop();
}
await LSDialog.dialog(
context: context,
title: 'Reset Configuration',
buttons: [
LSDialog.button(
text: 'Reset',
textColor: LSColors.red,
onPressed: () => _setValues(true),
),
],
content: [
LSDialog.textContent(text: 'Are you sure you want to clear your configuration?\n'),
LSDialog.textContent(text: 'You will be starting from a clean slate, please ensure you backup your current configuration first!'),
],
contentPadding: LSDialog.textDialogContentPadding(),
);
return [_flag];
}
static Future<List<dynamic>> enterEncryptionKey(BuildContext context) async {
bool _flag = false;
final _formKey = GlobalKey<FormState>();
final _textController = TextEditingController();
void _setValues(bool flag) {
if(_formKey.currentState.validate()) {
_flag = flag;
Navigator.of(context).pop();
}
}
await LSDialog.dialog(
context: context,
title: 'Decrypt Backup',
buttons: [
LSDialog.button(
text: 'Restore',
onPressed: () => _setValues(true),
),
],
content: [
LSDialog.textContent(text: 'Please enter the encryption key for this backup.'),
Form(
key: _formKey,
child: LSDialog.textFormInput(
controller: _textController,
title: 'Encryption Key',
obscureText: true,
onSubmitted: (_) => _setValues(true),
validator: (value) => value.length < 8
? 'Minimum of 8 characters'
: null,
),
),
],
contentPadding: LSDialog.inputTextDialogContentPadding(),
);
return [_flag, _textController.text];
}
static Future<List<dynamic>> backupConfiguration(BuildContext context) async {
bool _flag = false;
final _formKey = GlobalKey<FormState>();
final _textController = TextEditingController();
void _setValues(bool flag) {
if(_formKey.currentState.validate()) {
_flag = flag;
Navigator.of(context).pop();
}
}
await LSDialog.dialog(
context: context,
title: 'Backup Configuration',
buttons: [
LSDialog.button(
text: 'Backup',
textColor: LSColors.accent,
onPressed: () => _setValues(true),
),
],
content: [
LSDialog.richText(
children: [
LSDialog.textSpanContent(text: '\tAll backups are encrypted before being exported to the filesystem\n'),
LSDialog.textSpanContent(text: '\tThe backups do not contain customization options, only your configuration details\n'),
LSDialog.textSpanContent(text: '\tThe encryption key must be at least 8 characters\n'),
LSDialog.textSpanContent(text: '\tBackups can be found in '),
if(Platform.isIOS) LSDialog.bolded(text: '<On My Device>/LunaSea/configurations'),
if(Platform.isAndroid) LSDialog.bolded(text: '<Storage>/Android/data/app.lunasea.lunasea/files/configurations'),
],
),
Form(
key: _formKey,
child: LSDialog.textFormInput(
obscureText: true,
controller: _textController,
title: 'Encryption Key',
validator: (value) {
if(value.length < 8) {
return 'Minimum of 8 characters';
}
return null;
},
onSubmitted: (_) => _setValues(true),
),
),
],
contentPadding: LSDialog.inputTextDialogContentPadding(),
);
return [_flag, _textController.text];
}
static Future<List<dynamic>> disableSentryWarning(BuildContext context) async {
bool _flag = false;
void _setValues(bool flag) {
_flag = flag;
Navigator.of(context).pop();
}
await LSDialog.dialog(
context: context,
title: 'Disable Sentry Logging',
buttons: [
LSDialog.button(
text: 'Sentry Website',
onPressed: () => Constants.URL_SENTRY.lsLinks_OpenLink(),
textColor: LSColors.accent,
),
LSDialog.button(
text: 'Disable',
onPressed: () => _setValues(true),
textColor: LSColors.red,
),
],
content: [
LSDialog.richText(
children: [
LSDialog.bolded(
text: 'Error logs and stacktraces contain absolutely no identifying information on any users.\n\n',
color: LSColors.red,
fontSize: LSDialog.SUBBODY_SIZE,
),
LSDialog.textSpanContent(text: 'Sentry is an open-source platform used for capturing crashes and errors.\n\n'),
LSDialog.textSpanContent(text: 'To reserve your right to privacy, I have added the option to disable Sentry logging, but please know that these error logs and stacktraces are incredibly useful for catching and pinpointing bugs!\n\n'),
LSDialog.bolded(
text: 'A link to their website is available below for more information to help make an informed decision.',
color: LSColors.accent,
fontSize: LSDialog.SUBBODY_SIZE,
),
],
alignment: TextAlign.center,
),
],
contentPadding: LSDialog.textDialogContentPadding(),
);
return [_flag];
}
}