Compare commits

..

13 Commits

13 changed files with 764 additions and 614 deletions

View File

@ -1 +1,8 @@
const String addMountTitle = 'New Mount Settings';
const String appTitle = 'Repertory Management Portal';
const databaseTypeList = ['rocksdb', 'sqlite'];
const downloadTypeList = ['default', 'direct', 'ring_buffer'];
const eventLevelList = ['critical', 'error', 'warn', 'info', 'debug', 'trace'];
const protocolTypeList = ['http', 'https'];
const providerTypeList = ['Encrypt', 'Remote', 'S3', 'Sia'];
const ringBufferSizeList = ['128', '256', '512', '1024', '2048'];

View File

@ -4,6 +4,7 @@ String formatMountName(String type, String name) {
if (type == 'remote') {
return name.replaceAll('_', ':');
}
return name;
}
@ -11,6 +12,7 @@ String getBaseUri() {
if (kDebugMode || !kIsWeb) {
return 'http://127.0.0.1:30000';
}
return Uri.base.origin;
}
@ -25,3 +27,37 @@ String initialCaps(String txt) {
return txt[0].toUpperCase() + txt.substring(1).toLowerCase();
}
Map<String, dynamic> createDefaultSettings(String mountType) {
switch (mountType) {
case 'Encrypt':
return {
'EncryptConfig': {'EncryptionToken': '', 'Path': ''},
};
case 'Remote':
return {'EventLevel': 'info'};
case 'S3':
return {
'S3Config': {
'AccessKey': '',
'Bucket': '',
'Region': 'any',
'SecretKey': '',
'URL': '',
'UsePathStyle': false,
'UseRegionInURL': false,
},
};
case 'Sia':
return {
'HostConfig': {
'ApiPassword': '',
'ApiPort': '9980',
'HostNameOrIp': 'localhost',
},
'SiaConfig': {'Bucket': 'default'},
};
}
return {};
}

View File

@ -4,9 +4,9 @@ import 'package:repertory/constants.dart' as constants;
import 'package:repertory/helpers.dart';
import 'package:repertory/models/mount.dart';
import 'package:repertory/models/mount_list.dart';
import 'package:repertory/widgets/add_mount_widget.dart';
import 'package:repertory/widgets/mount_list_widget.dart';
import 'package:repertory/widgets/mount_settings.dart';
import 'package:repertory/screens/add_mount_screen.dart';
import 'package:repertory/screens/edit_mount_screen.dart';
import 'package:repertory/screens/home_screen.dart';
void main() {
runApp(
@ -14,9 +14,14 @@ void main() {
);
}
class MyApp extends StatelessWidget {
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(context) {
return MaterialApp(
@ -41,23 +46,23 @@ class MyApp extends StatelessWidget {
),
),
initialRoute: '/',
routes: {'/': (context) => const MyHomePage(title: constants.appTitle)},
routes: {
'/': (context) => const HomeScreen(title: constants.appTitle),
'/add':
(context) => const AddMountScreen(title: constants.addMountTitle),
},
onGenerateRoute: (settings) {
if (settings.name != '/settings') {
if (settings.name != '/edit') {
return null;
}
final mount = settings.arguments as Mount;
return MaterialPageRoute(
builder: (context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(
return EditMountScreen(
mount: mount,
title:
'${initialCaps(mount.type)} [${formatMountName(mount.type, mount.name)}] Settings',
),
),
body: MountSettingsWidget(mount: mount),
);
},
);
@ -65,152 +70,3 @@ class MyApp extends StatelessWidget {
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({super.key, required this.title});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _allowAdd = true;
String? _apiPassword;
String? _apiPort;
String? _bucket;
String? _encryptionToken;
String? _hostNameOrIp;
String _mountType = 'Encrypt';
String _mountName = "";
String? _path;
void _resetData() {
_apiPassword = null;
_apiPort = null;
_bucket = null;
_encryptionToken = null;
_hostNameOrIp = null;
_mountName = "";
_mountType = 'Encrypt';
_path = null;
}
void _updateData(String name, String? value) {
switch (name) {
case 'ApiPassword':
_apiPassword = value ?? '';
return;
case 'ApiPort':
_apiPort = value ?? '';
return;
case 'Bucket':
_bucket = value ?? '';
return;
case 'EncryptionToken':
_encryptionToken = value ?? '';
return;
case 'HostNameOrIp':
_hostNameOrIp = value ?? '';
return;
case 'Name':
_mountName = value ?? '';
return;
case 'Provider':
_mountType = value ?? 'Encrypt';
return;
case 'Path':
_path = value ?? '';
return;
}
}
@override
Widget build(context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
leading: const Icon(Icons.storage),
title: Text(widget.title),
),
body: MountListWidget(),
floatingActionButton: FloatingActionButton(
onPressed:
_allowAdd
? () async => showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Add Mount'),
content: Consumer<MountList>(
builder: (_, MountList mountList, __) {
return AddMountWidget(
mountType: _mountType,
onDataChanged: _updateData,
);
},
),
actions: [
TextButton(
child: const Text('Cancel'),
onPressed: () {
_resetData();
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('OK'),
onPressed: () {
setState(() => _allowAdd = false);
Provider.of<MountList>(context, listen: false)
.add(
_mountType,
_mountName,
apiPassword: _apiPassword,
apiPort: _apiPort,
bucket: _bucket,
encryptionToken: _encryptionToken,
hostNameOrIp: _hostNameOrIp,
path: _path,
)
.then((_) {
_resetData();
setState(() {
_allowAdd = true;
});
})
.catchError((_) {
setState(() => _allowAdd = true);
});
Navigator.of(context).pop();
},
),
],
);
},
)
: null,
tooltip: 'Add Mount',
child: const Icon(Icons.add),
),
);
}
@override
void setState(VoidCallback fn) {
if (!mounted) {
return;
}
super.setState(fn);
}
}

View File

@ -7,7 +7,10 @@ import 'package:repertory/types/mount_config.dart';
class Mount with ChangeNotifier {
final MountConfig mountConfig;
Mount(this.mountConfig) {
Mount(this.mountConfig, {isAdd = false}) {
if (isAdd) {
return;
}
refresh();
}
@ -17,48 +20,60 @@ class Mount with ChangeNotifier {
String get type => mountConfig.type;
Future<void> _fetch() async {
final response = await http.get(
Uri.parse(
Uri.encodeFull('${getBaseUri()}/api/v1/mount?name=$name&type=$type'),
),
);
try {
final response = await http.get(
Uri.parse(
Uri.encodeFull('${getBaseUri()}/api/v1/mount?name=$name&type=$type'),
),
);
if (response.statusCode != 200) {
return;
if (response.statusCode != 200) {
return;
}
mountConfig.updateSettings(jsonDecode(response.body));
notifyListeners();
} catch (e) {
debugPrint('$e');
}
mountConfig.updateSettings(jsonDecode(response.body));
notifyListeners();
}
Future<void> _fetchStatus() async {
final response = await http.get(
Uri.parse(
Uri.encodeFull(
'${getBaseUri()}/api/v1/mount_status?name=$name&type=$type',
try {
final response = await http.get(
Uri.parse(
Uri.encodeFull(
'${getBaseUri()}/api/v1/mount_status?name=$name&type=$type',
),
),
),
);
);
if (response.statusCode != 200) {
return;
if (response.statusCode != 200) {
return;
}
mountConfig.updateStatus(jsonDecode(response.body));
notifyListeners();
} catch (e) {
debugPrint('$e');
}
mountConfig.updateStatus(jsonDecode(response.body));
notifyListeners();
}
Future<void> mount(bool unmount, {String? location}) async {
await http.post(
Uri.parse(
Uri.encodeFull(
'${getBaseUri()}/api/v1/mount?unmount=$unmount&name=$name&type=$type&location=$location',
try {
await http.post(
Uri.parse(
Uri.encodeFull(
'${getBaseUri()}/api/v1/mount?unmount=$unmount&name=$name&type=$type&location=$location',
),
),
),
);
);
return refresh();
return refresh();
} catch (e) {
debugPrint('$e');
}
}
Future<void> refresh() async {
@ -67,30 +82,40 @@ class Mount with ChangeNotifier {
}
Future<void> setValue(String key, String value) async {
await http.put(
Uri.parse(
Uri.encodeFull(
'${getBaseUri()}/api/v1/set_value_by_name?name=$name&type=$type&key=$key&value=$value',
try {
await http.put(
Uri.parse(
Uri.encodeFull(
'${getBaseUri()}/api/v1/set_value_by_name?name=$name&type=$type&key=$key&value=$value',
),
),
),
);
);
return refresh();
return refresh();
} catch (e) {
debugPrint('$e');
}
}
Future<String?> getMountLocation() async {
final response = await http.get(
Uri.parse(
Uri.encodeFull(
'${getBaseUri()}/api/v1/get_mount_location?name=$name&type=$type',
try {
final response = await http.get(
Uri.parse(
Uri.encodeFull(
'${getBaseUri()}/api/v1/get_mount_location?name=$name&type=$type',
),
),
),
);
);
if (response.statusCode != 200) {
return null;
if (response.statusCode != 200) {
return null;
}
return jsonDecode(response.body)['Location'] as String;
} catch (e) {
debugPrint('$e');
}
return jsonDecode(response.body)['Location'] as String;
return null;
}
}

View File

@ -17,11 +17,14 @@ class MountList with ChangeNotifier {
UnmodifiableListView<MountConfig>(_mountList);
Future<void> _fetch() async {
final response = await http.get(
Uri.parse('${getBaseUri()}/api/v1/mount_list'),
);
try {
final response = await http.get(
Uri.parse('${getBaseUri()}/api/v1/mount_list'),
);
if (response.statusCode == 200) {
if (response.statusCode != 200) {
return;
}
List<MountConfig> nextList = [];
jsonDecode(response.body).forEach((key, value) {
@ -33,7 +36,8 @@ class MountList with ChangeNotifier {
_mountList = nextList;
notifyListeners();
return;
} catch (e) {
debugPrint('$e');
}
}
@ -50,25 +54,22 @@ class MountList with ChangeNotifier {
Future<void> add(
String type,
String name, {
String? apiPassword,
String? apiPort,
String? bucket,
String? encryptionToken,
String? hostNameOrIp,
String? path,
}) async {
await http.post(
Uri.parse(
Uri.encodeFull(
'${getBaseUri()}/api/v1/add_mount?name=$name&type=$type&bucket=$bucket'
'&path=$path&apiPassword=$apiPassword&apiPort=$apiPort&hostNameOrIp=$hostNameOrIp'
'&encryptionToken=$encryptionToken',
String name,
Map<String, dynamic> mountConfig,
) async {
try {
await http.post(
Uri.parse(
Uri.encodeFull(
'${getBaseUri()}/api/v1/add_mount?name=$name&type=$type&config=${jsonEncode(mountConfig)}',
),
),
),
);
);
return _fetch();
return _fetch();
} catch (e) {
debugPrint('$e');
}
}
void remove(String name) {

View File

@ -0,0 +1,165 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:repertory/constants.dart';
import 'package:repertory/helpers.dart';
import 'package:repertory/models/mount.dart';
import 'package:repertory/models/mount_list.dart';
import 'package:repertory/types/mount_config.dart';
import 'package:repertory/widgets/mount_settings.dart';
class AddMountScreen extends StatefulWidget {
final String title;
const AddMountScreen({super.key, required this.title});
@override
State<AddMountScreen> createState() => _AddMountScreenState();
}
class _AddMountScreenState extends State<AddMountScreen> {
static const _padding = 15.0;
Mount? _mount;
final _mountNameController = TextEditingController();
String _mountType = "";
final Map<String, Map<String, dynamic>> _settings = {
"": {},
"Encrypt": createDefaultSettings("Encrypt"),
"Remote": createDefaultSettings("Remote"),
"S3": createDefaultSettings("S3"),
"Sia": createDefaultSettings("Sia"),
};
bool _showAdvanced = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
actions: [
Row(
children: [
const Text("Advanced"),
IconButton(
icon: Icon(_showAdvanced ? Icons.toggle_on : Icons.toggle_off),
onPressed: () => setState(() => _showAdvanced = !_showAdvanced),
),
],
),
],
),
body: Padding(
padding: const EdgeInsets.all(_padding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Card(
child: Padding(
padding: const EdgeInsets.all(_padding),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const Text('Provider Type'),
const SizedBox(width: _padding),
DropdownButton<String>(
value: _mountType,
onChanged: (mountType) => _handleChange(mountType ?? ''),
items:
providerTypeList.map<DropdownMenuItem<String>>((
item,
) {
return DropdownMenuItem<String>(
value: item,
child: Text(item),
);
}).toList(),
),
],
),
),
),
if (_mountType.isNotEmpty) const SizedBox(height: _padding),
if (_mountType.isNotEmpty)
Card(
child: Padding(
padding: const EdgeInsets.all(_padding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const Text('Configuration Name'),
const SizedBox(width: _padding),
TextField(
autofocus: true,
controller: _mountNameController,
keyboardType: TextInputType.text,
onChanged: (_) => _handleChange(_mountType),
),
],
),
),
),
if (_mount != null)
Expanded(
child: Card(
child: Padding(
padding: const EdgeInsets.all(_padding),
child: MountSettingsWidget(
isAdd: true,
mount: _mount!,
settings: _settings[_mountType]!,
showAdvanced: _showAdvanced,
),
),
),
),
if (_mount != null)
ElevatedButton.icon(
onPressed: () {
Provider.of<MountList>(context, listen: false).add(
_mountType,
_mountNameController.text,
_settings[_mountType]!,
);
Navigator.pop(context);
},
label: const Text('Add'),
icon: Icon(Icons.add),
),
],
),
),
);
}
void _handleChange(String mountType) {
setState(() {
_mountType = mountType;
_mount =
(_mountNameController.text.isEmpty)
? null
: Mount(
MountConfig(
name: _mountNameController.text,
settings: _settings[mountType],
type: mountType,
),
isAdd: true,
);
});
}
@override
void setState(VoidCallback fn) {
if (!mounted) {
return;
}
super.setState(fn);
}
}

View File

@ -0,0 +1,53 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:repertory/models/mount.dart';
import 'package:repertory/widgets/mount_settings.dart';
class EditMountScreen extends StatefulWidget {
final Mount mount;
final String title;
const EditMountScreen({super.key, required this.mount, required this.title});
@override
State<EditMountScreen> createState() => _EditMountScreenState();
}
class _EditMountScreenState extends State<EditMountScreen> {
bool _showAdvanced = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
actions: [
Row(
children: [
const Text("Advanced"),
IconButton(
icon: Icon(_showAdvanced ? Icons.toggle_on : Icons.toggle_off),
onPressed: () => setState(() => _showAdvanced = !_showAdvanced),
),
],
),
],
),
body: MountSettingsWidget(
mount: widget.mount,
settings: jsonDecode(jsonEncode(widget.mount.mountConfig.settings)),
showAdvanced: _showAdvanced,
),
);
}
@override
void setState(VoidCallback fn) {
if (!mounted) {
return;
}
super.setState(fn);
}
}

View File

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:repertory/widgets/mount_list_widget.dart';
class HomeScreen extends StatefulWidget {
final String title;
const HomeScreen({super.key, required this.title});
@override
State<HomeScreen> createState() => _HomeScreeState();
}
class _HomeScreeState extends State<HomeScreen> {
@override
Widget build(context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
leading: const Icon(Icons.storage),
title: Text(widget.title),
),
body: MountListWidget(),
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.pushNamed(context, '/add'),
tooltip: 'Add Mount',
child: const Icon(Icons.add),
),
);
}
@override
void setState(VoidCallback fn) {
if (!mounted) {
return;
}
super.setState(fn);
}
}

View File

@ -7,7 +7,13 @@ class MountConfig {
Map<String, dynamic> _settings = {};
IconData _state = Icons.toggle_off;
final String _type;
MountConfig({required name, required type}) : _name = name, _type = type;
MountConfig({required name, required type, Map<String, dynamic>? settings})
: _name = name,
_type = type {
if (settings != null) {
_settings = settings;
}
}
String get name => _name;
String get path => _path;

View File

@ -1,125 +0,0 @@
import 'package:flutter/material.dart';
class AddMountWidget extends StatefulWidget {
final String mountType;
final void Function(String name, String? value) onDataChanged;
const AddMountWidget({
super.key,
required this.mountType,
required this.onDataChanged,
});
@override
State<AddMountWidget> createState() => _AddMountWidgetState();
}
class _AddMountWidgetState extends State<AddMountWidget> {
static const _items = <String>['Encrypt', 'Remote', 'S3', 'Sia'];
static const _padding = 15.0;
late String _mountType;
List<Widget> _createTextField(
String title,
String dataName, {
String? value,
}) {
if (value != null) {
widget.onDataChanged(dataName, value);
}
return [
const SizedBox(height: _padding),
Text(
title,
textAlign: TextAlign.left,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold,
),
),
TextField(
autofocus: dataName == 'Name',
decoration: InputDecoration(),
onChanged: (value) => widget.onDataChanged(dataName, value),
controller: TextEditingController(text: value),
),
];
}
@override
Widget build(BuildContext context) {
final mountTypeLower = _mountType.toLowerCase();
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Provider Type',
textAlign: TextAlign.left,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: _padding),
DropdownButton<String>(
value: _mountType,
onChanged: (value) {
widget.onDataChanged('Provider', value);
setState(() {
_mountType = value ?? "";
});
},
items:
_items.map<DropdownMenuItem<String>>((item) {
return DropdownMenuItem<String>(
value: item,
child: Text(item),
);
}).toList(),
),
],
),
if (mountTypeLower != 'remote')
..._createTextField('Configuration Name', 'Name'),
if (mountTypeLower == 'encrypt') ..._createTextField('Path', 'Path'),
if (mountTypeLower == 'sia')
..._createTextField('ApiPassword', 'ApiPassword'),
if (mountTypeLower == 's3' || mountTypeLower == 'sia')
..._createTextField(
'Bucket',
'Bucket',
value: mountTypeLower == 'sia' ? 'default' : null,
),
if (mountTypeLower == 'remote' || mountTypeLower == 'sia')
..._createTextField(
'HostNameOrIp',
'HostNameOrIp',
value: 'localhost',
),
if (mountTypeLower == 'remote' || mountTypeLower == 'sia')
..._createTextField(
'ApiPort',
'ApiPort',
value: mountTypeLower == 'sia' ? '9980' : null,
),
if (mountTypeLower == 'encrypt' || mountTypeLower == 'remote')
..._createTextField('EncryptionToken', 'EncryptionToken'),
],
);
}
@override
void initState() {
_mountType = widget.mountType;
super.initState();
}
}

View File

@ -10,7 +10,7 @@ class MountListWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<MountList>(
builder: (context, mountList, _) {
builder: (context, MountList mountList, _) {
return ListView.builder(
itemBuilder: (context, idx) {
return ChangeNotifierProvider(

View File

@ -1,87 +1,102 @@
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:repertory/constants.dart';
import 'package:repertory/models/mount.dart';
import 'package:settings_ui/settings_ui.dart';
class MountSettingsWidget extends StatefulWidget {
final bool isAdd;
final bool showAdvanced;
final Mount mount;
const MountSettingsWidget({super.key, required this.mount});
final Function? onChanged;
final Map<String, dynamic> settings;
const MountSettingsWidget({
super.key,
this.isAdd = false,
required this.mount,
this.onChanged,
required this.settings,
required this.showAdvanced,
});
@override
State<MountSettingsWidget> createState() => _MountSettingsWidgetState();
}
class _MountSettingsWidgetState extends State<MountSettingsWidget> {
Map<String, dynamic> _settings = {};
void _addBooleanSetting(list, root, key, value) {
list.add(
SettingsTile.switchTile(
leading: Icon(Icons.quiz),
title: Text(key),
initialValue: (value as bool),
onPressed: (_) {
setState(() {
root[key] = !value;
});
},
onToggle: (bool nextValue) {
setState(() {
root[key] = nextValue;
});
},
),
);
void _addBooleanSetting(list, root, key, value, isAdvanced) {
if (!isAdvanced || widget.showAdvanced) {
list.add(
SettingsTile.switchTile(
leading: Icon(Icons.quiz),
title: Text(key),
initialValue: (value as bool),
onPressed: (_) {
setState(() {
root[key] = !value;
widget.onChanged?.call(widget.settings);
});
},
onToggle: (bool nextValue) {
setState(() {
root[key] = nextValue;
widget.onChanged?.call(widget.settings);
});
},
),
);
}
}
void _addIntSetting(list, root, key, value) {
list.add(
SettingsTile.navigation(
leading: Icon(Icons.onetwothree),
title: Text(key),
value: Text(value.toString()),
onPressed: (_) {
String updatedValue = value.toString();
showDialog(
context: context,
builder: (context) {
return AlertDialog(
actions: [
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
void _addIntSetting(list, root, key, value, isAdvanced) {
if (!isAdvanced || widget.showAdvanced) {
list.add(
SettingsTile.navigation(
leading: Icon(Icons.onetwothree),
title: Text(key),
value: Text(value.toString()),
onPressed: (_) {
String updatedValue = value.toString();
showDialog(
context: context,
builder: (context) {
return AlertDialog(
actions: [
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('OK'),
onPressed: () {
setState(() {
root[key] = int.parse(updatedValue);
widget.onChanged?.call(widget.settings);
});
Navigator.of(context).pop();
},
),
],
content: TextField(
autofocus: true,
controller: TextEditingController(text: updatedValue),
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
keyboardType: TextInputType.number,
onChanged: (nextValue) {
updatedValue = nextValue;
},
),
TextButton(
child: Text('OK'),
onPressed: () {
setState(() {
root[key] = int.parse(updatedValue);
});
Navigator.of(context).pop();
},
),
],
content: TextField(
autofocus: true,
controller: TextEditingController(text: updatedValue),
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
keyboardType: TextInputType.number,
onChanged: (nextValue) {
updatedValue = nextValue;
},
),
title: Text(key),
);
},
);
},
),
);
title: Text(key),
);
},
);
},
),
);
}
}
void _addIntListSetting(
@ -92,101 +107,127 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
List<String> valueList,
defaultValue,
icon,
isAdvanced,
) {
list.add(
SettingsTile.navigation(
title: Text(key),
leading: Icon(icon),
value: DropdownButton<String>(
value: value.toString(),
onChanged: (newValue) {
setState(() {
root[key] = int.parse(newValue ?? defaultValue.toString());
});
},
items:
valueList.map<DropdownMenuItem<String>>((item) {
return DropdownMenuItem<String>(value: item, child: Text(item));
}).toList(),
),
),
);
}
void _addListSetting(list, root, key, value, List<String> valueList, icon) {
list.add(
SettingsTile.navigation(
title: Text(key),
leading: Icon(icon),
value: DropdownButton<String>(
value: value,
onChanged: (newValue) {
setState(() {
root[key] = newValue;
});
},
items:
valueList.map<DropdownMenuItem<String>>((item) {
return DropdownMenuItem<String>(value: item, child: Text(item));
}).toList(),
),
),
);
}
void _addPasswordSetting(list, root, key, value) {
list.add(
SettingsTile.navigation(
leading: Icon(Icons.password),
title: Text(key),
value: Text('*' * (value as String).length),
),
);
}
void _addStringSetting(list, root, key, value, icon) {
list.add(
SettingsTile.navigation(
leading: Icon(icon),
title: Text(key),
value: Text(value),
onPressed: (_) {
String updatedValue = value;
showDialog(
context: context,
builder: (context) {
return AlertDialog(
actions: [
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('OK'),
onPressed: () {
setState(() {
root[key] = updatedValue;
});
Navigator.of(context).pop();
},
),
],
content: TextField(
autofocus: true,
controller: TextEditingController(text: updatedValue),
onChanged: (value) {
updatedValue = value;
},
),
title: Text(key),
);
if (!isAdvanced || widget.showAdvanced) {
list.add(
SettingsTile.navigation(
title: Text(key),
leading: Icon(icon),
value: DropdownButton<String>(
value: value.toString(),
onChanged: (newValue) {
setState(() {
root[key] = int.parse(newValue ?? defaultValue.toString());
widget.onChanged?.call(widget.settings);
});
},
);
},
),
);
items:
valueList.map<DropdownMenuItem<String>>((item) {
return DropdownMenuItem<String>(
value: item,
child: Text(item),
);
}).toList(),
),
),
);
}
}
void _addListSetting(
list,
root,
key,
value,
List<String> valueList,
icon,
isAdvanced,
) {
if (!isAdvanced || widget.showAdvanced) {
list.add(
SettingsTile.navigation(
title: Text(key),
leading: Icon(icon),
value: DropdownButton<String>(
value: value,
onChanged: (newValue) {
setState(() {
root[key] = newValue;
widget.onChanged?.call(widget.settings);
});
},
items:
valueList.map<DropdownMenuItem<String>>((item) {
return DropdownMenuItem<String>(
value: item,
child: Text(item),
);
}).toList(),
),
),
);
}
}
void _addPasswordSetting(list, root, key, value, isAdvanced) {
if (!isAdvanced || widget.showAdvanced) {
list.add(
SettingsTile.navigation(
leading: Icon(Icons.password),
title: Text(key),
value: Text('*' * (value as String).length),
),
);
}
}
void _addStringSetting(list, root, key, value, icon, isAdvanced) {
if (!isAdvanced || widget.showAdvanced) {
list.add(
SettingsTile.navigation(
leading: Icon(icon),
title: Text(key),
value: Text(value),
onPressed: (_) {
String updatedValue = value;
showDialog(
context: context,
builder: (context) {
return AlertDialog(
actions: [
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('OK'),
onPressed: () {
setState(() {
root[key] = updatedValue;
widget.onChanged?.call(widget.settings);
});
Navigator.of(context).pop();
},
),
],
content: TextField(
autofocus: true,
controller: TextEditingController(text: updatedValue),
onChanged: (value) {
updatedValue = value;
},
),
title: Text(key),
);
},
);
},
),
);
}
}
@override
@ -199,77 +240,97 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
List<SettingsTile> s3ConfigSettings = [];
List<SettingsTile> siaConfigSettings = [];
_settings.forEach((key, value) {
widget.settings.forEach((key, value) {
if (key == 'ApiAuth') {
_addPasswordSetting(commonSettings, _settings, key, value);
_addPasswordSetting(commonSettings, widget.settings, key, value, true);
} else if (key == 'ApiPort') {
_addIntSetting(commonSettings, _settings, key, value);
_addIntSetting(commonSettings, widget.settings, key, value, true);
} else if (key == 'ApiUser') {
_addStringSetting(commonSettings, _settings, key, value, Icons.person);
_addStringSetting(
commonSettings,
widget.settings,
key,
value,
Icons.person,
true,
);
} else if (key == 'DatabaseType') {
_addListSetting(commonSettings, _settings, key, value, [
'rocksdb',
'sqlite',
], Icons.dataset);
_addListSetting(
commonSettings,
widget.settings,
key,
value,
databaseTypeList,
Icons.dataset,
true,
);
} else if (key == 'DownloadTimeoutSeconds') {
_addIntSetting(commonSettings, _settings, key, value);
_addIntSetting(commonSettings, widget.settings, key, value, true);
} else if (key == 'EnableDownloadTimeout') {
_addBooleanSetting(commonSettings, _settings, key, value);
_addBooleanSetting(commonSettings, widget.settings, key, value, true);
} else if (key == 'EnableDriveEvents') {
_addBooleanSetting(commonSettings, _settings, key, value);
_addBooleanSetting(commonSettings, widget.settings, key, value, true);
} else if (key == 'EventLevel') {
_addListSetting(commonSettings, _settings, key, value, [
'critical',
'error',
'warn',
'info',
'debug',
'trace',
], Icons.event);
_addListSetting(
commonSettings,
widget.settings,
key,
value,
eventLevelList,
Icons.event,
false,
);
} else if (key == 'EvictionDelayMinutes') {
_addIntSetting(commonSettings, _settings, key, value);
_addIntSetting(commonSettings, widget.settings, key, value, true);
} else if (key == 'EvictionUseAccessedTime') {
_addBooleanSetting(commonSettings, _settings, key, value);
_addBooleanSetting(commonSettings, widget.settings, key, value, true);
} else if (key == 'MaxCacheSizeBytes') {
_addIntSetting(commonSettings, _settings, key, value);
_addIntSetting(commonSettings, widget.settings, key, value, false);
} else if (key == 'MaxUploadCount') {
_addIntSetting(commonSettings, _settings, key, value);
_addIntSetting(commonSettings, widget.settings, key, value, true);
} else if (key == 'OnlineCheckRetrySeconds') {
_addIntSetting(commonSettings, _settings, key, value);
_addIntSetting(commonSettings, widget.settings, key, value, true);
} else if (key == 'PreferredDownloadType') {
_addListSetting(commonSettings, _settings, key, value, [
'default',
'direct',
'ring_buffer',
], Icons.download);
_addListSetting(
commonSettings,
widget.settings,
key,
value,
downloadTypeList,
Icons.download,
false,
);
} else if (key == 'RetryReadCount') {
_addIntSetting(commonSettings, _settings, key, value);
_addIntSetting(commonSettings, widget.settings, key, value, true);
} else if (key == 'RingBufferFileSize') {
_addIntListSetting(
commonSettings,
_settings,
widget.settings,
key,
value,
['128', '256', '512', '1024', '2048'],
ringBufferSizeList,
512,
Icons.animation,
false,
);
} else if (key == 'EncryptConfig') {
value.forEach((subKey, subValue) {
if (subKey == 'EncryptionToken') {
_addPasswordSetting(
encryptConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
false,
);
} else if (subKey == 'Path') {
_addStringSetting(
encryptConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
Icons.folder,
false,
);
}
});
@ -278,64 +339,72 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
if (subKey == 'AgentString') {
_addStringSetting(
hostConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
Icons.support_agent,
true,
);
} else if (subKey == 'ApiPassword') {
_addPasswordSetting(
hostConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
false,
);
} else if (subKey == 'ApiPort') {
_addIntSetting(
hostConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
false,
);
} else if (subKey == 'ApiUser') {
_addStringSetting(
hostConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
Icons.person,
true,
);
} else if (subKey == 'HostNameOrIp') {
_addStringSetting(
hostConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
Icons.computer,
false,
);
} else if (subKey == 'Path') {
_addStringSetting(
hostConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
Icons.route,
true,
);
} else if (subKey == 'Protocol') {
_addListSetting(
hostConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
['http', 'https'],
protocolTypeList,
Icons.http,
true,
);
} else if (subKey == 'TimeoutMs') {
_addIntSetting(
hostConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
true,
);
}
});
@ -344,45 +413,51 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
if (subKey == 'ApiPort') {
_addIntSetting(
remoteConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
false,
);
} else if (subKey == 'EncryptionToken') {
_addPasswordSetting(
remoteConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
false,
);
} else if (subKey == 'HostNameOrIp') {
_addStringSetting(
remoteConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
Icons.computer,
false,
);
} else if (subKey == 'MaxConnections') {
_addIntSetting(
remoteConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
true,
);
} else if (subKey == 'ReceiveTimeoutMs') {
_addIntSetting(
remoteConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
true,
);
} else if (subKey == 'SendTimeoutMs') {
_addIntSetting(
remoteConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
true,
);
}
});
@ -390,28 +465,37 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
value.forEach((subKey, subValue) {
if (subKey == 'Enable') {
List<SettingsTile> tempSettings = [];
_addBooleanSetting(tempSettings, _settings[key], subKey, subValue);
_addBooleanSetting(
tempSettings,
widget.settings[key],
subKey,
subValue,
false,
);
remoteMountSettings.insertAll(0, tempSettings);
} else if (subKey == 'ApiPort') {
_addIntSetting(
remoteMountSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
false,
);
} else if (subKey == 'ClientPoolSize') {
_addIntSetting(
remoteMountSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
true,
);
} else if (subKey == 'EncryptionToken') {
_addPasswordSetting(
remoteMountSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
false,
);
}
});
@ -420,63 +504,77 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
if (subKey == 'AccessKey') {
_addPasswordSetting(
s3ConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
false,
);
} else if (subKey == 'Bucket') {
_addStringSetting(
s3ConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
Icons.folder,
false,
);
} else if (subKey == 'EncryptionToken') {
_addPasswordSetting(
s3ConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
false,
);
} else if (subKey == 'Region') {
_addStringSetting(
s3ConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
Icons.map,
false,
);
} else if (subKey == 'SecretKey') {
_addPasswordSetting(
s3ConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
false,
);
} else if (subKey == 'TimeoutMs') {
_addIntSetting(s3ConfigSettings, _settings[key], subKey, subValue);
_addIntSetting(
s3ConfigSettings,
widget.settings[key],
subKey,
subValue,
true,
);
} else if (subKey == 'URL') {
_addStringSetting(
s3ConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
Icons.http,
false,
);
} else if (subKey == 'UsePathStyle') {
_addBooleanSetting(
s3ConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
false,
);
} else if (subKey == 'UseRegionInURL') {
_addBooleanSetting(
s3ConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
false,
);
}
});
@ -485,10 +583,11 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
if (subKey == 'Bucket') {
_addStringSetting(
siaConfigSettings,
_settings[key],
widget.settings[key],
subKey,
subValue,
Icons.folder,
false,
);
}
});
@ -527,51 +626,40 @@ class _MountSettingsWidgetState extends State<MountSettingsWidget> {
SettingsSection(
title: const Text('Remote Mount'),
tiles:
_settings['RemoteMount']['Enable'] as bool
widget.settings['RemoteMount']['Enable'] as bool
? remoteMountSettings
: [remoteMountSettings[0]],
),
SettingsSection(title: const Text('Settings'), tiles: commonSettings),
if (commonSettings.isNotEmpty)
SettingsSection(title: const Text('Settings'), tiles: commonSettings),
],
);
}
@override
void dispose() {
var settings = widget.mount.mountConfig.settings;
if (!DeepCollectionEquality().equals(_settings, settings)) {
_settings.forEach((key, value) {
if (!DeepCollectionEquality().equals(settings[key], value)) {
if (value is Map<String, dynamic>) {
value.forEach((subKey, subValue) {
if (!DeepCollectionEquality().equals(
settings[key][subKey],
subValue,
)) {
widget.mount.setValue('$key.$subKey', subValue.toString());
}
});
} else {
widget.mount.setValue(key, value.toString());
if (!widget.isAdd) {
var settings = widget.mount.mountConfig.settings;
if (!DeepCollectionEquality().equals(widget.settings, settings)) {
widget.settings.forEach((key, value) {
if (!DeepCollectionEquality().equals(settings[key], value)) {
if (value is Map<String, dynamic>) {
value.forEach((subKey, subValue) {
if (!DeepCollectionEquality().equals(
settings[key][subKey],
subValue,
)) {
widget.mount.setValue('$key.$subKey', subValue.toString());
}
});
} else {
widget.mount.setValue(key, value.toString());
}
}
}
});
});
}
}
super.dispose();
}
@override
void initState() {
_settings = jsonDecode(jsonEncode(widget.mount.mountConfig.settings));
super.initState();
}
@override
void setState(VoidCallback fn) {
if (!mounted) {
return;
}
super.setState(fn);
}
}

View File

@ -38,7 +38,7 @@ class _MountWidgetState extends State<MountWidget> {
leading: IconButton(
icon: Icon(Icons.settings, color: textColor),
onPressed: () {
Navigator.pushNamed(context, '/settings', arguments: mount);
Navigator.pushNamed(context, '/edit', arguments: mount);
},
),
subtitle: Column(