Refactor add server form

This commit is contained in:
Juan Gilsanz Polo 2023-10-07 18:37:16 +02:00
parent 1f1edf7d98
commit 2389e34571
10 changed files with 749 additions and 906 deletions

View file

@ -4,6 +4,7 @@
"connect": "Connect",
"servers": "Servers",
"createConnection": "Create connection",
"editConnection": "Edit connection",
"name": "Name",
"ipDomain": "IP address or domain",
"path": "Path",

View file

@ -4,6 +4,7 @@
"connect": "Conectar",
"servers": "Servidores",
"createConnection": "Crear conexión",
"editConnection": "Editar conexión",
"name": "Nombre",
"ipDomain": "Dirección IP o dominio",
"path": "Ruta",

View file

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:adguard_home_manager/widgets/add_server_modal.dart';
import 'package:adguard_home_manager/widgets/version_warning_modal.dart';
import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart';
class FabConnect extends StatelessWidget {
const FabConnect({Key? key}) : super(key: key);
@ -12,37 +11,7 @@ class FabConnect extends StatelessWidget {
void openAddServerModal() async {
await Future.delayed(const Duration(seconds: 0), (() => {
if (width > 700) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AddServerModal(
window: true,
onUnsupportedVersion: (version) => showDialog(
context: context,
builder: (ctx) => VersionWarningModal(
version: version
),
barrierDismissible: false
),
),
)
}
else {
Navigator.push(context, MaterialPageRoute(
fullscreenDialog: true,
builder: (BuildContext context) => AddServerModal(
window: false,
onUnsupportedVersion: (version) => showDialog(
context: context,
builder: (ctx) => VersionWarningModal(
version: version
),
barrierDismissible: false
),
)
))
}
openServerFormModal(context: context, width: width)
}));
}

View file

@ -6,9 +6,8 @@ import 'package:flutter/rendering.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import 'package:adguard_home_manager/widgets/version_warning_modal.dart';
import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart';
import 'package:adguard_home_manager/widgets/servers_list/servers_list.dart';
import 'package:adguard_home_manager/widgets/add_server_modal.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart';
@ -69,37 +68,7 @@ class _ServersState extends State<Servers> {
void openAddServerModal() async {
await Future.delayed(const Duration(seconds: 0), (() => {
if (width > 700) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AddServerModal(
window: true,
onUnsupportedVersion: (version) => showDialog(
context: context,
builder: (ctx) => VersionWarningModal(
version: version
),
barrierDismissible: false
),
),
)
}
else {
Navigator.push(context, MaterialPageRoute(
fullscreenDialog: true,
builder: (BuildContext context) => AddServerModal(
window: false,
onUnsupportedVersion: (version) => showDialog(
context: context,
builder: (ctx) => VersionWarningModal(
version: version
),
barrierDismissible: false
),
)
))
}
openServerFormModal(context: context, width: width)
}));
}

View file

@ -0,0 +1,120 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/models/server.dart';
import 'package:adguard_home_manager/widgets/add_server/add_server_modal.dart';
import 'package:adguard_home_manager/widgets/version_warning_modal.dart';
bool checkDataValid({
required TextEditingController nameController,
required TextEditingController ipDomainController,
required String? ipDomainError,
required String? pathError,
required String? portError,
}) {
if (
nameController.text != '' &&
ipDomainController.text != '' &&
ipDomainError == null &&
pathError == null &&
portError == null
) {
return true;
}
else {
return false;
}
}
String? validatePort({
required String? value,
required BuildContext context
}) {
if (value != null && value != '') {
if (int.tryParse(value) != null && int.parse(value) <= 65535) {
return null;
}
else {
return AppLocalizations.of(context)!.invalidPort;
}
}
else {
return null;
}
}
String? validateSubroute({
required BuildContext context,
required String? value
}) {
if (value != null && value != '') {
RegExp subrouteRegexp = RegExp(r'^\/\b([A-Za-z0-9_\-~/]*)[^\/|\.|\:]$');
if (subrouteRegexp.hasMatch(value) == true) {
return null;
}
else {
return AppLocalizations.of(context)!.invalidPath;
}
}
else {
return null;
}
}
String? validateAddress({
required BuildContext context,
required String? value
}) {
if (value != null && value != '') {
RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$');
RegExp domain = RegExp(r'^(([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+)|((\w|-)+)$');
if (ipAddress.hasMatch(value) == true || domain.hasMatch(value) == true) {
return null;
}
else {
return AppLocalizations.of(context)!.invalidIpDomain;
}
}
else {
return AppLocalizations.of(context)!.ipDomainNotEmpty;
}
}
void openServerFormModal({
required BuildContext context,
required double width,
Server? server,
}) {
showGeneralDialog(
context: context,
barrierColor: width <= 700
?Colors.transparent
: Colors.black54,
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: Tween(
begin: const Offset(0, 1),
end: const Offset(0, 0)
).animate(
CurvedAnimation(
parent: anim1,
curve: Curves.easeInOutCubicEmphasized
)
),
child: child,
);
},
pageBuilder: (context, animation, secondaryAnimation) => AddServerModal(
fullScreen: width <= 700,
server: server,
onUnsupportedVersion: (version) => showDialog(
context: context,
builder: (ctx) => VersionWarningModal(
version: version
),
barrierDismissible: false
),
),
);
}

View file

@ -0,0 +1,561 @@
// ignore_for_file: use_build_context_synchronously
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/widgets/add_server/form_text_field.dart';
import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart';
import 'package:adguard_home_manager/functions/snackbar.dart';
import 'package:adguard_home_manager/constants/urls.dart';
import 'package:adguard_home_manager/functions/open_url.dart';
import 'package:adguard_home_manager/constants/enums.dart';
import 'package:adguard_home_manager/providers/status_provider.dart';
import 'package:adguard_home_manager/functions/base64.dart';
import 'package:adguard_home_manager/services/http_requests.dart';
import 'package:adguard_home_manager/models/app_log.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart';
import 'package:adguard_home_manager/models/server.dart';
enum ConnectionType { http, https}
class AddServerModal extends StatefulWidget {
final Server? server;
final bool fullScreen;
final void Function(String version) onUnsupportedVersion;
const AddServerModal({
Key? key,
this.server,
required this.fullScreen,
required this.onUnsupportedVersion
}) : super(key: key);
@override
State<AddServerModal> createState() => _AddServerModalState();
}
class _AddServerModalState extends State<AddServerModal> {
final uuid = const Uuid();
final TextEditingController nameController = TextEditingController();
String? nameError;
ConnectionType connectionType = ConnectionType.http;
final TextEditingController ipDomainController = TextEditingController();
String? ipDomainError;
final TextEditingController pathController = TextEditingController();
String? pathError;
final TextEditingController portController = TextEditingController();
String? portError;
final TextEditingController userController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
bool defaultServer = false;
bool homeAssistant = false;
bool allDataValid = false;
bool isConnecting = false;
Widget sectionLabel(String label) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 24
),
child: Text(
label,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.primary
),
),
);
}
@override
void initState() {
if (widget.server != null) {
nameController.text = widget.server!.name;
connectionType = widget.server!.connectionMethod == 'https' ? ConnectionType.https : ConnectionType.http;
ipDomainController.text = widget.server!.domain;
pathController.text = widget.server!.path ?? '';
portController.text = widget.server!.port != null ? widget.server!.port.toString() : "";
userController.text = widget.server!.user ?? "";
passwordController.text = widget.server!.password ?? "";
defaultServer = widget.server!.defaultServer;
homeAssistant = widget.server!.runningOnHa;
}
setState(() => allDataValid = checkDataValid(
ipDomainController: ipDomainController,
nameController: nameController,
ipDomainError: ipDomainError,
pathError: pathError,
portError: portError
));
super.initState();
}
@override
Widget build(BuildContext context) {
final serversProvider = Provider.of<ServersProvider>(context, listen: false);
final statusProvider = Provider.of<StatusProvider>(context, listen: false);
final appConfigProvider = Provider.of<AppConfigProvider>(context, listen: false);
void cancelConnecting() {
if (mounted) {
setState(() => isConnecting = false);
}
else {
isConnecting = false;
}
}
void validateData() {
setState(() => allDataValid = checkDataValid(
ipDomainController: ipDomainController,
nameController: nameController,
ipDomainError: ipDomainError,
pathError: pathError,
portError: portError
));
}
String getErrorMessage(String message) {
if (message == 'invalid_username_password') return AppLocalizations.of(context)!.invalidUsernamePassword;
if (message == 'many_attempts') return AppLocalizations.of(context)!.tooManyAttempts;
if (message == 'no_connection') return AppLocalizations.of(context)!.cantReachServer;
if (message == 'server_error') return AppLocalizations.of(context)!.serverError;
return AppLocalizations.of(context)!.unknownError;
}
void connect() async {
Server serverObj = Server(
id: uuid.v4(),
name: nameController.text,
connectionMethod: connectionType.name,
domain: ipDomainController.text,
port: portController.text != '' ? int.parse(portController.text) : null,
user: userController.text != "" ? userController.text : null,
password: passwordController.text != "" ? passwordController.text : null,
defaultServer: defaultServer,
authToken: homeAssistant == true
? encodeBase64UserPass(userController.text, passwordController.text)
: null,
runningOnHa: homeAssistant
);
setState(() => isConnecting = true);
final result = homeAssistant == true
? await loginHA(serverObj)
: await login(serverObj);
// If something goes wrong with the connection
if (result['result'] != 'success') {
cancelConnecting();
appConfigProvider.addLog(result['log']);
if (mounted) {
return showSnacbkar(
appConfigProvider: appConfigProvider,
label: getErrorMessage(result['result']),
color: Colors.red
);
}
}
if (serverObj.user != null && serverObj.password != null) {
serverObj.authToken = encodeBase64UserPass(serverObj.user!, serverObj.password!);
}
final serverCreated = await serversProvider.createServer(serverObj);
// If something goes wrong when saving the connection on the db
if (serverCreated == null) {
if (mounted) setState(() => isConnecting = false);
appConfigProvider.addLog(
AppLog(
type: 'save_connection_db',
dateTime: DateTime.now(),
message: serverCreated.toString()
)
);
if (mounted) {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.connectionNotCreated,
color: Colors.red
);
}
}
statusProvider.setServerStatusLoad(LoadStatus.loading);
final ApiClient apiClient = ApiClient(server: serverObj);
final serverStatus = await apiClient.getServerStatus();
// If something goes wrong when fetching server status
if (serverStatus['result'] != 'success') {
appConfigProvider.addLog(serverStatus['log']);
statusProvider.setServerStatusLoad(LoadStatus.error);
Navigator.pop(context);
}
// If everything is successful
statusProvider.setServerStatusData(
data: serverStatus['data']
);
serversProvider.setApiClient(apiClient);
statusProvider.setServerStatusLoad(LoadStatus.loaded);
if (serverStatus['data'].serverVersion.contains('a') || serverStatus['data'].serverVersion.contains('b')) {
Navigator.pop(context);
widget.onUnsupportedVersion(serverStatus['data'].serverVersion);
}
else {
Navigator.pop(context);
}
}
void edit() async {
final Server serverObj = Server(
id: widget.server!.id,
name: nameController.text,
connectionMethod: connectionType.name,
domain: ipDomainController.text,
port: portController.text != '' ? int.parse(portController.text) : null,
user: userController.text != "" ? userController.text : null,
password: passwordController.text != "" ? passwordController.text : null,
defaultServer: defaultServer,
authToken: homeAssistant == true
? encodeBase64UserPass(userController.text, passwordController.text)
: null,
runningOnHa: homeAssistant
);
final result = homeAssistant == true
? await loginHA(serverObj)
: await login(serverObj);
// If something goes wrong with the connection
if (result['result'] != 'success') {
cancelConnecting();
appConfigProvider.addLog(result['log']);
if (mounted) {
return showSnacbkar(
appConfigProvider: appConfigProvider,
label: getErrorMessage(result['result']),
color: Colors.red
);
}
}
if (serverObj.user != null && serverObj.password != null) {
serverObj.authToken = encodeBase64UserPass(serverObj.user!, serverObj.password!);
}
final serverSaved = await serversProvider.editServer(serverObj);
// If something goes wrong when saving the connection on the db
if (serverSaved == null) {
if (mounted) setState(() => isConnecting = false);
appConfigProvider.addLog(
AppLog(
type: 'save_connection_db',
dateTime: DateTime.now(),
message: serverSaved.toString()
)
);
if (mounted) {
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.connectionNotCreated,
color: Colors.red
);
}
}
// If everything is successful
final ApiClient apiClient = ApiClient(server: serverObj);
final version = await apiClient.getServerVersion();
if (
version['result'] == 'success' &&
(version['data'].contains('a') || version['data'].contains('b')) // alpha or beta
) {
Navigator.pop(context);
widget.onUnsupportedVersion(version['data']);
}
else {
Navigator.pop(context);
}
}
Widget actions() {
return Row(
children: [
IconButton(
onPressed: () => openUrl(Urls.connectionInstructions),
icon: const Icon(Icons.help_outline_outlined)
),
IconButton(
tooltip: widget.server == null
? AppLocalizations.of(context)!.connect
: AppLocalizations.of(context)!.save,
onPressed: allDataValid == true && isConnecting == false
? widget.server == null
? () => connect()
: () => edit()
: null,
icon: isConnecting
? const CircularProgressIndicator()
: Icon(
widget.server == null
? Icons.login_rounded
: Icons.save_rounded
)
),
],
);
}
List<Widget> form() {
return [
Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
margin: const EdgeInsets.only(
top: 24,
left: 24,
right: 24
),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withOpacity(0.05),
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: Theme.of(context).colorScheme.primary
)
),
child: Text(
"${connectionType.name}://${ipDomainController.text}${portController.text != '' ? ':${portController.text}' : ""}${pathController.text}",
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.w500
),
),
),
sectionLabel(AppLocalizations.of(context)!.general),
FormTextField(
label: AppLocalizations.of(context)!.name,
controller: nameController,
icon: Icons.badge_rounded,
error: nameError,
onChanged: (value) {
if (value != '') {
setState(() => nameError = null);
}
else {
setState(() => nameError = AppLocalizations.of(context)!.nameNotEmpty);
}
validateData();
},
isConnecting: isConnecting,
),
sectionLabel(AppLocalizations.of(context)!.connection),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: SegmentedButton<ConnectionType>(
segments: const [
ButtonSegment(
value: ConnectionType.http,
label: Text("HTTP")
),
ButtonSegment(
value: ConnectionType.https,
label: Text("HTTPS")
),
],
selected: <ConnectionType>{connectionType},
onSelectionChanged: (value) => setState(() => connectionType = value.first),
),
),
const SizedBox(height: 30),
FormTextField(
label: AppLocalizations.of(context)!.ipDomain,
controller: ipDomainController,
icon: Icons.link_rounded,
error: ipDomainError,
keyboardType: TextInputType.url,
onChanged: (v) {
setState(() => ipDomainError = validateAddress(context: context, value: v));
validateData();
},
isConnecting: isConnecting,
),
const SizedBox(height: 20),
FormTextField(
label: AppLocalizations.of(context)!.path,
controller: pathController,
icon: Icons.route_rounded,
error: pathError,
onChanged: (v) {
setState(() => pathError = validateSubroute(context: context, value: v));
validateData();
},
hintText: AppLocalizations.of(context)!.examplePath,
helperText: AppLocalizations.of(context)!.helperPath,
isConnecting: isConnecting,
),
const SizedBox(height: 20),
FormTextField(
label: AppLocalizations.of(context)!.port,
controller: portController,
icon: Icons.numbers_rounded,
error: portError,
keyboardType: TextInputType.number,
onChanged: (v) {
setState(() => portError = validatePort(context: context, value: v));
validateData();
},
isConnecting: isConnecting,
),
sectionLabel(AppLocalizations.of(context)!.authentication),
FormTextField(
label: AppLocalizations.of(context)!.username,
controller: userController,
icon: Icons.person_rounded,
isConnecting: isConnecting,
),
const SizedBox(height: 20),
FormTextField(
label: AppLocalizations.of(context)!.password,
controller: passwordController,
icon: Icons.lock_rounded,
keyboardType: TextInputType.visiblePassword,
obscureText: true,
isConnecting: isConnecting,
),
sectionLabel(AppLocalizations.of(context)!.other),
Material(
color: Colors.transparent,
child: InkWell(
onTap: widget.server == null
? () => setState(() => defaultServer = !defaultServer)
: null,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
AppLocalizations.of(context)!.defaultServer,
style: const TextStyle(
fontSize: 15,
),
),
Switch(
value: defaultServer,
onChanged: widget.server == null
? (value) => setState(() => defaultServer = value)
: null,
)
],
),
),
),
),
const SizedBox(height: 20),
Material(
color: Colors.transparent,
child: InkWell(
onTap: () => setState(() => homeAssistant = !homeAssistant),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
AppLocalizations.of(context)!.runningHomeAssistant,
style: const TextStyle(
fontSize: 15,
),
),
Switch(
value: homeAssistant,
onChanged: (value) => setState(() => homeAssistant = value),
)
],
),
),
),
),
const SizedBox(height: 20),
];
}
if (widget.fullScreen == true) {
return Dialog.fullscreen(
child: Scaffold(
appBar: AppBar(
title: widget.server != null
? Text(AppLocalizations.of(context)!.createConnection)
: Text(AppLocalizations.of(context)!.editConnection),
actions: [
actions(),
const SizedBox(width: 8)
],
),
body: ListView(
children: form()
),
),
);
}
else {
return Dialog(
child: SizedBox(
width: 400,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.clear_rounded)
),
const SizedBox(width: 8),
Text(
AppLocalizations.of(context)!.createConnection,
style: const TextStyle(
fontSize: 20
),
),
],
),
actions()
],
),
),
Expanded(
child: ListView(
children: form()
),
)
],
),
),
);
}
}
}

View file

@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
class FormTextField extends StatelessWidget {
final TextEditingController controller;
final String label;
final String? error;
final IconData icon;
final TextInputType? keyboardType;
final Function(String)? onChanged;
final bool? obscureText;
final String? hintText;
final String? helperText;
final bool isConnecting;
const FormTextField({
Key? key,
required this.label,
required this.controller,
this.error,
required this.icon,
this.keyboardType,
this.onChanged,
this.obscureText,
this.hintText,
this.helperText,
required this.isConnecting
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: TextFormField(
controller: controller,
onChanged: onChanged,
obscureText: obscureText ?? false,
enabled: !isConnecting,
decoration: InputDecoration(
prefixIcon: Icon(icon),
errorText: error,
hintText: hintText,
helperText: helperText,
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
labelText: label,
),
keyboardType: keyboardType,
),
);
}
}

View file

@ -1,766 +0,0 @@
// ignore_for_file: use_build_context_synchronously
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/providers/app_config_provider.dart';
import 'package:adguard_home_manager/functions/snackbar.dart';
import 'package:adguard_home_manager/constants/urls.dart';
import 'package:adguard_home_manager/functions/open_url.dart';
import 'package:adguard_home_manager/constants/enums.dart';
import 'package:adguard_home_manager/providers/status_provider.dart';
import 'package:adguard_home_manager/functions/base64.dart';
import 'package:adguard_home_manager/services/http_requests.dart';
import 'package:adguard_home_manager/models/app_log.dart';
import 'package:adguard_home_manager/providers/servers_provider.dart';
import 'package:adguard_home_manager/models/server.dart';
enum ConnectionType { http, https}
class AddServerModal extends StatefulWidget {
final Server? server;
final bool window;
final void Function(String version) onUnsupportedVersion;
const AddServerModal({
Key? key,
this.server,
required this.window,
required this.onUnsupportedVersion
}) : super(key: key);
@override
State<AddServerModal> createState() => _AddServerModalState();
}
class _AddServerModalState extends State<AddServerModal> {
final uuid = const Uuid();
final TextEditingController nameController = TextEditingController();
String? nameError;
ConnectionType connectionType = ConnectionType.http;
final TextEditingController ipDomainController = TextEditingController();
String? ipDomainError;
final TextEditingController pathController = TextEditingController();
String? pathError;
final TextEditingController portController = TextEditingController();
String? portError;
final TextEditingController userController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
bool defaultServer = false;
bool homeAssistant = false;
bool allDataValid = false;
bool isConnecting = false;
Widget sectionLabel(String label) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 24
),
child: Text(
label,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.primary
),
),
);
}
Widget textField({
required String label,
required TextEditingController controller,
String? error,
required IconData icon,
TextInputType? keyboardType,
Function(String)? onChanged,
bool? obscureText,
String? hintText,
String? helperText
}) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: TextFormField(
controller: controller,
onChanged: onChanged,
obscureText: obscureText ?? false,
decoration: InputDecoration(
prefixIcon: Icon(icon),
errorText: error,
hintText: hintText,
helperText: helperText,
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10)
)
),
labelText: label,
),
keyboardType: keyboardType,
),
);
}
void checkDataValid() {
if (
nameController.text != '' &&
ipDomainController.text != '' &&
ipDomainError == null &&
pathError == null &&
portError == null
) {
setState(() {
allDataValid = true;
});
}
else {
setState(() {
allDataValid = false;
});
}
}
void validatePort(String? value) {
if (value != null && value != '') {
if (int.tryParse(value) != null && int.parse(value) <= 65535) {
setState(() {
portError = null;
});
}
else {
setState(() {
portError = AppLocalizations.of(context)!.invalidPort;
});
}
}
else {
setState(() {
portError = null;
});
}
checkDataValid();
}
void validateSubroute(String? value) {
if (value != null && value != '') {
RegExp subrouteRegexp = RegExp(r'^\/\b([A-Za-z0-9_\-~/]*)[^\/|\.|\:]$');
if (subrouteRegexp.hasMatch(value) == true) {
setState(() {
pathError = null;
});
}
else {
setState(() {
pathError = AppLocalizations.of(context)!.invalidPath;
});
}
}
else {
setState(() {
pathError = null;
});
}
checkDataValid();
}
void validateAddress(String? value) {
if (value != null && value != '') {
RegExp ipAddress = RegExp(r'^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$');
RegExp domain = RegExp(r'^(([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+)|((\w|-)+)$');
if (ipAddress.hasMatch(value) == true || domain.hasMatch(value) == true) {
setState(() {
ipDomainError = null;
});
}
else {
setState(() {
ipDomainError = AppLocalizations.of(context)!.invalidIpDomain;
});
}
}
else {
setState(() {
ipDomainError = AppLocalizations.of(context)!.ipDomainNotEmpty;
});
}
checkDataValid();
}
@override
void initState() {
if (widget.server != null) {
nameController.text = widget.server!.name;
connectionType = widget.server!.connectionMethod == 'https' ? ConnectionType.https : ConnectionType.http;
ipDomainController.text = widget.server!.domain;
pathController.text = widget.server!.path ?? '';
portController.text = widget.server!.port != null ? widget.server!.port.toString() : "";
userController.text = widget.server!.user ?? "";
passwordController.text = widget.server!.password ?? "";
defaultServer = widget.server!.defaultServer;
homeAssistant = widget.server!.runningOnHa;
}
checkDataValid();
super.initState();
}
@override
Widget build(BuildContext context) {
final serversProvider = Provider.of<ServersProvider>(context, listen: false);
final statusProvider = Provider.of<StatusProvider>(context, listen: false);
final appConfigProvider = Provider.of<AppConfigProvider>(context, listen: false);
final mediaQuery = MediaQuery.of(context);
void cancelConnecting() {
if (mounted) {
setState(() => isConnecting = false);
}
else {
isConnecting = false;
}
}
void connect() async {
Server serverObj = Server(
id: uuid.v4(),
name: nameController.text,
connectionMethod: connectionType.name,
domain: ipDomainController.text,
port: portController.text != '' ? int.parse(portController.text) : null,
user: userController.text != "" ? userController.text : null,
password: passwordController.text != "" ? passwordController.text : null,
defaultServer: defaultServer,
authToken: homeAssistant == true
? encodeBase64UserPass(userController.text, passwordController.text)
: null,
runningOnHa: homeAssistant
);
setState(() => isConnecting = true);
final result = homeAssistant == true
? await loginHA(serverObj)
: await login(serverObj);
if (!mounted) return;
if (result['result'] == 'success') {
if (serverObj.user != null && serverObj.password != null) {
serverObj.authToken = encodeBase64UserPass(serverObj.user!, serverObj.password!);
}
final serverCreated = await serversProvider.createServer(serverObj);
if (serverCreated == null) {
statusProvider.setServerStatusLoad(LoadStatus.loading);
final ApiClient apiClient = ApiClient(server: serverObj);
final serverStatus = await apiClient.getServerStatus();
if (!mounted) return;
if (serverStatus['result'] == 'success') {
statusProvider.setServerStatusData(
data: serverStatus['data']
);
serversProvider.setApiClient(apiClient);
statusProvider.setServerStatusLoad(LoadStatus.loaded);
if (serverStatus['data'].serverVersion.contains('a') || serverStatus['data'].serverVersion.contains('b')) {
Navigator.pop(context);
widget.onUnsupportedVersion(serverStatus['data'].serverVersion);
}
else {
Navigator.pop(context);
}
}
else {
appConfigProvider.addLog(serverStatus['log']);
statusProvider.setServerStatusLoad(LoadStatus.error);
Navigator.pop(context);
}
}
else {
setState(() => isConnecting = false);
appConfigProvider.addLog(
AppLog(
type: 'save_connection_db',
dateTime: DateTime.now(),
message: serverCreated.toString()
)
);
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.connectionNotCreated,
color: Colors.red
);
}
}
else if (result['result'] == 'invalid_username_password') {
cancelConnecting();
appConfigProvider.addLog(result['log']);
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.invalidUsernamePassword,
color: Colors.red
);
}
else if (result['result'] == 'many_attempts') {
cancelConnecting();
appConfigProvider.addLog(result['log']);
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.tooManyAttempts,
color: Colors.red
);
}
else if (result['result'] == 'no_connection') {
cancelConnecting();
appConfigProvider.addLog(result['log']);
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.cantReachServer,
color: Colors.red
);
}
else if (result['result'] == 'ssl_error') {
cancelConnecting();
appConfigProvider.addLog(result['log']);
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.sslError,
color: Colors.red
);
}
else if (result['result'] == 'server_error') {
cancelConnecting();
appConfigProvider.addLog(result['log']);
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.serverError,
color: Colors.red
);
}
else {
cancelConnecting();
appConfigProvider.addLog(result['log']);
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.unknownError,
color: Colors.red
);
}
}
void edit() async {
final Server serverObj = Server(
id: widget.server!.id,
name: nameController.text,
connectionMethod: connectionType.name,
domain: ipDomainController.text,
port: portController.text != '' ? int.parse(portController.text) : null,
user: userController.text != "" ? userController.text : null,
password: passwordController.text != "" ? passwordController.text : null,
defaultServer: defaultServer,
authToken: homeAssistant == true
? encodeBase64UserPass(userController.text, passwordController.text)
: null,
runningOnHa: homeAssistant
);
final result = homeAssistant == true
? await loginHA(serverObj)
: await login(serverObj);
if (!mounted) return;
if (result['result'] == 'success') {
if (serverObj.user != null && serverObj.password != null) {
serverObj.authToken = encodeBase64UserPass(serverObj.user!, serverObj.password!);
}
final serverSaved = await serversProvider.editServer(serverObj);
if (!mounted) return;
if (serverSaved == null) {
final ApiClient apiClient = ApiClient(server: serverObj);
final version = await apiClient.getServerVersion();
if (
version['result'] == 'success' &&
(version['data'].contains('a') || version['data'].contains('b')) // alpha or beta
) {
Navigator.pop(context);
widget.onUnsupportedVersion(version['data']);
}
else {
Navigator.pop(context);
}
}
else {
appConfigProvider.addLog(
AppLog(
type: 'edit_connection_db',
dateTime: DateTime.now(),
message: serverSaved.toString()
)
);
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.connectionNotCreated,
color: Colors.red
);
}
}
else if (result['result'] == 'invalid_username_password') {
appConfigProvider.addLog(result['log']);
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.invalidUsernamePassword,
color: Colors.red
);
}
else if (result['result'] == 'many_attempts') {
appConfigProvider.addLog(result['log']);
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.tooManyAttempts,
color: Colors.red
);
}
else if (result['result'] == 'no_connection') {
appConfigProvider.addLog(result['log']);
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.cantReachServer,
color: Colors.red
);
}
else if (result['result'] == 'ssl_error') {
appConfigProvider.addLog(result['log']);
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.sslError,
color: Colors.red
);
}
else if (result['result'] == 'server_error') {
appConfigProvider.addLog(result['log']);
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.serverError,
color: Colors.red
);
}
else {
appConfigProvider.addLog(result['log']);
showSnacbkar(
appConfigProvider: appConfigProvider,
label: AppLocalizations.of(context)!.unknownError,
color: Colors.red
);
}
}
List<Widget> form() {
return [
Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
margin: const EdgeInsets.only(
top: 24,
left: 24,
right: 24
),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withOpacity(0.05),
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: Theme.of(context).colorScheme.primary
)
),
child: Text(
"${connectionType.name}://${ipDomainController.text}${portController.text != '' ? ':${portController.text}' : ""}${pathController.text}",
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.w500
),
),
),
sectionLabel(AppLocalizations.of(context)!.general),
textField(
label: AppLocalizations.of(context)!.name,
controller: nameController,
icon: Icons.badge_rounded,
error: nameError,
onChanged: (value) {
if (value != '') {
setState(() => nameError = null);
}
else {
setState(() => nameError = AppLocalizations.of(context)!.nameNotEmpty);
}
checkDataValid();
}
),
sectionLabel(AppLocalizations.of(context)!.connection),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: SegmentedButton<ConnectionType>(
segments: const [
ButtonSegment(
value: ConnectionType.http,
label: Text("HTTP")
),
ButtonSegment(
value: ConnectionType.https,
label: Text("HTTPS")
),
],
selected: <ConnectionType>{connectionType},
onSelectionChanged: (value) => setState(() => connectionType = value.first),
),
),
const SizedBox(height: 30),
textField(
label: AppLocalizations.of(context)!.ipDomain,
controller: ipDomainController,
icon: Icons.link_rounded,
error: ipDomainError,
keyboardType: TextInputType.url,
onChanged: validateAddress
),
const SizedBox(height: 20),
textField(
label: AppLocalizations.of(context)!.path,
controller: pathController,
icon: Icons.route_rounded,
error: pathError,
onChanged: validateSubroute,
hintText: AppLocalizations.of(context)!.examplePath,
helperText: AppLocalizations.of(context)!.helperPath,
),
const SizedBox(height: 20),
textField(
label: AppLocalizations.of(context)!.port,
controller: portController,
icon: Icons.numbers_rounded,
error: portError,
keyboardType: TextInputType.number,
onChanged: validatePort
),
sectionLabel(AppLocalizations.of(context)!.authentication),
textField(
label: AppLocalizations.of(context)!.username,
controller: userController,
icon: Icons.person_rounded,
),
const SizedBox(height: 20),
textField(
label: AppLocalizations.of(context)!.password,
controller: passwordController,
icon: Icons.lock_rounded,
keyboardType: TextInputType.visiblePassword,
obscureText: true
),
sectionLabel(AppLocalizations.of(context)!.other),
Material(
color: Colors.transparent,
child: InkWell(
onTap: widget.server == null
? () => setState(() => defaultServer = !defaultServer)
: null,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
AppLocalizations.of(context)!.defaultServer,
style: const TextStyle(
fontSize: 15,
),
),
Switch(
value: defaultServer,
onChanged: widget.server == null
? (value) => setState(() => defaultServer = value)
: null,
)
],
),
),
),
),
const SizedBox(height: 20),
Material(
color: Colors.transparent,
child: InkWell(
onTap: () => setState(() => homeAssistant = !homeAssistant),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
AppLocalizations.of(context)!.runningHomeAssistant,
style: const TextStyle(
fontSize: 15,
),
),
Switch(
value: homeAssistant,
onChanged: (value) => setState(() => homeAssistant = value),
)
],
),
),
),
),
const SizedBox(height: 20),
];
}
if (widget.window == true) {
return Dialog(
child: SizedBox(
width: 400,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.clear_rounded)
),
const SizedBox(width: 8),
Text(
AppLocalizations.of(context)!.createConnection,
style: const TextStyle(
fontSize: 20
),
),
],
),
Row(
children: [
IconButton(
onPressed: () => openUrl(Urls.connectionInstructions),
icon: const Icon(Icons.help_outline_outlined)
),
IconButton(
tooltip: widget.server == null
? AppLocalizations.of(context)!.connect
: AppLocalizations.of(context)!.save,
onPressed: allDataValid == true
? widget.server == null
? () => connect()
: () => edit()
: null,
icon: Icon(
widget.server == null
? Icons.login_rounded
: Icons.save_rounded
)
),
],
),
],
),
),
Expanded(
child: ListView(
children: form()
),
)
],
),
),
);
}
else {
return Stack(
children: [
Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.createConnection),
actions: [
IconButton(
onPressed: () => openUrl(Urls.connectionInstructions),
icon: const Icon(Icons.help_outline_outlined)
),
IconButton(
tooltip: widget.server == null
? AppLocalizations.of(context)!.connect
: AppLocalizations.of(context)!.save,
onPressed: allDataValid == true
? widget.server == null
? () => connect()
: () => edit()
: null,
icon: Icon(
widget.server == null
? Icons.login_rounded
: Icons.save_rounded
)
),
const SizedBox(width: 10)
],
toolbarHeight: 70,
),
body: ListView(
children: form(),
)
),
AnimatedOpacity(
opacity: isConnecting == true ? 1 : 0,
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
child: IgnorePointer(
ignoring: isConnecting == true ? false : true,
child: Scaffold(
backgroundColor: Colors.transparent,
body: Container(
width: mediaQuery.size.width,
height: mediaQuery.size.height,
color: const Color.fromRGBO(0, 0, 0, 0.7),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const CircularProgressIndicator(
color: Colors.white,
),
const SizedBox(height: 30),
Text(
AppLocalizations.of(context)!.connecting,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
fontSize: 26
),
)
],
),
),
),
),
)
],
);
}
}
}

View file

@ -5,8 +5,7 @@ import 'package:expandable/expandable.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/widgets/version_warning_modal.dart';
import 'package:adguard_home_manager/widgets/add_server_modal.dart';
import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart';
import 'package:adguard_home_manager/widgets/servers_list/delete_modal.dart';
import 'package:adguard_home_manager/classes/process_modal.dart';
@ -88,41 +87,9 @@ class _ServersListItemState extends State<ServersListItem> with SingleTickerProv
});
}
void openAddServerBottomSheet({Server? server}) async {
void openServerModal({Server? server}) async {
await Future.delayed(const Duration(seconds: 0), (() => {
if (width > 700) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AddServerModal(
server: server,
window: true,
onUnsupportedVersion: (version) => showDialog(
context: context,
builder: (ctx) => VersionWarningModal(
version: version
),
barrierDismissible: false
),
),
)
}
else {
Navigator.push(context, MaterialPageRoute(
fullscreenDialog: true,
builder: (BuildContext context) => AddServerModal(
server: server,
window: false,
onUnsupportedVersion: (version) => showDialog(
context: context,
builder: (ctx) => VersionWarningModal(
version: version
),
barrierDismissible: false
),
)
))
}
openServerFormModal(context: context, width: width, server: server)
}));
}
@ -326,7 +293,7 @@ class _ServersListItemState extends State<ServersListItem> with SingleTickerProv
)
),
PopupMenuItem(
onTap: (() => openAddServerBottomSheet(server: server)),
onTap: (() => openServerModal(server: server)),
child: Row(
children: [
const Icon(Icons.edit),

View file

@ -4,8 +4,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:adguard_home_manager/widgets/version_warning_modal.dart';
import 'package:adguard_home_manager/widgets/add_server_modal.dart';
import 'package:adguard_home_manager/widgets/add_server/add_server_functions.dart';
import 'package:adguard_home_manager/widgets/servers_list/delete_modal.dart';
import 'package:adguard_home_manager/classes/process_modal.dart';
@ -57,41 +56,9 @@ class _ServersTileItemState extends State<ServersTileItem> with SingleTickerProv
});
}
void openAddServerBottomSheet({Server? server}) async {
void openServerModal({Server? server}) async {
await Future.delayed(const Duration(seconds: 0), (() => {
if (width > 700) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AddServerModal(
server: server,
window: true,
onUnsupportedVersion: (version) => showDialog(
context: context,
builder: (ctx) => VersionWarningModal(
version: version
),
barrierDismissible: false
),
),
)
}
else {
Navigator.push(context, MaterialPageRoute(
fullscreenDialog: true,
builder: (BuildContext context) => AddServerModal(
server: server,
window: false,
onUnsupportedVersion: (version) => showDialog(
context: context,
builder: (ctx) => VersionWarningModal(
version: version
),
barrierDismissible: false
),
)
))
}
openServerFormModal(context: context, width: width, server: server)
}));
}
@ -291,7 +258,7 @@ class _ServersTileItemState extends State<ServersTileItem> with SingleTickerProv
)
),
PopupMenuItem(
onTap: (() => openAddServerBottomSheet(server: server)),
onTap: (() => openServerModal(server: server)),
child: Row(
children: [
const Icon(Icons.edit),