Files
vpn/lib/views/settings_view.dart
2026-04-05 11:54:37 +03:00

145 lines
4.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
import 'package:ionicons/ionicons.dart';
import 'package:vpn/models/storage_model.dart';
import 'package:vpn/services/vpn_service.dart';
// ignore: must_be_immutable
class SettingsView extends StatefulWidget {
SettingsView({super.key, required this.model, required this.vpnService});
StorageModel model;
VpnService vpnService;
@override
State<SettingsView> createState() => _SettingsViewState();
}
class _SettingsViewState extends State<SettingsView> {
final _key = GlobalKey<ExpandableFabState>();
Future<void> _changeSelectedItem(int index) async {
setState(() {
widget.model.selected = index;
});
await widget.vpnService.syncActiveConfig(widget.model);
}
Future<void> _removeItem(int index) async {
setState(() {
widget.model.removeConfig(index);
});
await widget.vpnService.syncActiveConfig(widget.model);
}
Future<void> _addItemFromClipboard(BuildContext context) async {
final data = await Clipboard.getData(Clipboard.kTextPlain);
if (!context.mounted) return;
final String config = data?.text?.trim() ?? '';
if (config.isEmpty) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Буфер обмена пуст')));
return;
}
final String? name = widget.vpnService.getName(config);
if (name == null) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Ошибка импорта')));
return;
}
setState(() {
widget.model.addConfig(name, config);
});
await widget.vpnService.syncActiveConfig(widget.model);
if (!context.mounted) return;
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Конфигурация импортирована')));
}
void _closeFab() {
final state = _key.currentState;
if (state != null && state.isOpen) {
state.toggle();
}
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(title: const Text("Settings"), centerTitle: true),
body: ListView.separated(
padding: const EdgeInsets.all(16),
itemCount: widget.model.configs.length + 1,
separatorBuilder: (_, _) => const SizedBox(height: 10),
itemBuilder: (context, index) {
if (index == widget.model.configs.length) {
return const SizedBox(height: 40);
}
return Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).hoverColor,
borderRadius: BorderRadius.circular(8),
),
child: ListTile(
leading: widget.model.selected == index
? Icon(Ionicons.checkmark)
: null,
title: Text(widget.model.configs[index].name),
onTap: () async => _changeSelectedItem(index),
),
),
),
const SizedBox(width: 8),
IconButton.filled(
onPressed: () async => _removeItem(index),
icon: const Icon(Ionicons.trash),
),
],
);
},
),
floatingActionButtonLocation: ExpandableFab.location,
floatingActionButton: ExpandableFab(
key: _key,
openButtonBuilder: RotateFloatingActionButtonBuilder(
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
child: const Icon(Ionicons.add),
),
closeButtonBuilder: RotateFloatingActionButtonBuilder(
backgroundColor: colorScheme.primary,
foregroundColor: colorScheme.onPrimary,
child: const Icon(Ionicons.close),
),
children: [
FloatingActionButton.extended(
heroTag: null,
foregroundColor: colorScheme.onPrimary,
backgroundColor: colorScheme.primary,
icon: const Icon(Ionicons.clipboard),
label: const Text("From clipboard"),
onPressed: () {
_addItemFromClipboard(context);
_closeFab();
},
),
],
),
);
}
}