From 33125e543c327a3da95b57c1e63f3c0eb524b267 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 30 Apr 2023 20:18:29 +0200 Subject: [PATCH] Adapted servers list and connection screen --- lib/screens/connect/connect.dart | 1 + lib/screens/connect/fab.dart | 23 +- lib/screens/servers/servers.dart | 33 +- lib/widgets/add_server_modal.dart | 460 ++++++++++-------- lib/widgets/process_dialog.dart | 1 + lib/widgets/servers_list/servers_list.dart | 43 +- .../servers_list/servers_list_item.dart | 26 +- .../servers_list/servers_tile_item.dart | 381 +++++++++++++++ 8 files changed, 743 insertions(+), 225 deletions(-) create mode 100644 lib/widgets/servers_list/servers_tile_item.dart diff --git a/lib/screens/connect/connect.dart b/lib/screens/connect/connect.dart index ff55daf..0e19480 100644 --- a/lib/screens/connect/connect.dart +++ b/lib/screens/connect/connect.dart @@ -66,6 +66,7 @@ class _ConnectState extends State { controllers: expandableControllerList, onChange: expandOrContract, scrollController: scrollController, + breakingWidth: 700, ), AnimatedPositioned( duration: const Duration(milliseconds: 100), diff --git a/lib/screens/connect/fab.dart b/lib/screens/connect/fab.dart index 0c3204d..c20cafc 100644 --- a/lib/screens/connect/fab.dart +++ b/lib/screens/connect/fab.dart @@ -6,12 +6,27 @@ class FabConnect extends StatelessWidget { @override Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + void openAddServerModal() async { await Future.delayed(const Duration(seconds: 0), (() => { - Navigator.push(context, MaterialPageRoute( - fullscreenDialog: true, - builder: (BuildContext context) => const AddServerModal() - )) + if (width > 700) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const AddServerModal( + window: true, + ), + ) + } + else { + Navigator.push(context, MaterialPageRoute( + fullscreenDialog: true, + builder: (BuildContext context) => const AddServerModal( + window: false, + ) + )) + } })); } diff --git a/lib/screens/servers/servers.dart b/lib/screens/servers/servers.dart index 9e98e29..ba1e7b3 100644 --- a/lib/screens/servers/servers.dart +++ b/lib/screens/servers/servers.dart @@ -13,7 +13,12 @@ import 'package:adguard_home_manager/providers/servers_provider.dart'; import 'package:adguard_home_manager/providers/app_config_provider.dart'; class Servers extends StatefulWidget { - const Servers({Key? key}) : super(key: key); + final double? breakingWidth; + + const Servers({ + Key? key, + this.breakingWidth + }) : super(key: key); @override State createState() => _ServersState(); @@ -55,16 +60,31 @@ class _ServersState extends State { final serversProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + for (var i = 0; i < serversProvider.serversList.length; i++) { expandableControllerList.add(ExpandableController()); } void openAddServerModal() async { await Future.delayed(const Duration(seconds: 0), (() => { - Navigator.push(context, MaterialPageRoute( - fullscreenDialog: true, - builder: (BuildContext context) => const AddServerModal() - )) + if (width > 700) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const AddServerModal( + window: true, + ), + ) + } + else { + Navigator.push(context, MaterialPageRoute( + fullscreenDialog: true, + builder: (BuildContext context) => const AddServerModal( + window: false, + ) + )) + } })); } @@ -79,7 +99,8 @@ class _ServersState extends State { context: context, controllers: expandableControllerList, onChange: expandOrContract, - scrollController: scrollController + scrollController: scrollController, + breakingWidth: widget.breakingWidth ?? 700, ), AnimatedPositioned( duration: const Duration(milliseconds: 100), diff --git a/lib/widgets/add_server_modal.dart b/lib/widgets/add_server_modal.dart index 7fc830d..a332c58 100644 --- a/lib/widgets/add_server_modal.dart +++ b/lib/widgets/add_server_modal.dart @@ -16,10 +16,12 @@ enum ConnectionType { http, https} class AddServerModal extends StatefulWidget { final Server? server; + final bool window; const AddServerModal({ Key? key, this.server, + required this.window }) : super(key: key); @override @@ -429,15 +431,227 @@ class _AddServerModalState extends State { } } - return Stack( - children: [ - Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context)!.createConnection), - actions: [ + List 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( + segments: const [ + ButtonSegment( + value: ConnectionType.http, + label: Text("HTTP") + ), + ButtonSegment( + value: ConnectionType.https, + label: Text("HTTPS") + ), + ], + selected: {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.only(right: 10), - child: IconButton( + 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 + ), + ), + ], + ), + 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( tooltip: widget.server == null ? AppLocalizations.of(context)!.connect : AppLocalizations.of(context)!.save, @@ -452,203 +666,49 @@ class _AddServerModalState extends State { : Icons.save_rounded ) ), - ), - ], - toolbarHeight: 70, + const SizedBox(width: 10) + ], + toolbarHeight: 70, + ), + body: ListView( + children: form(), + ) ), - body: ListView( - children: [ - 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( - segments: const [ - ButtonSegment( - value: ConnectionType.http, - label: Text("HTTP") - ), - ButtonSegment( - value: ConnectionType.https, - label: Text("HTTPS") - ), - ], - selected: {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), - ], - ), - ), - 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( + 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, - fontWeight: FontWeight.w500, - fontSize: 26 ), - ) - ], + const SizedBox(height: 30), + Text( + AppLocalizations.of(context)!.connecting, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500, + fontSize: 26 + ), + ) + ], + ), ), ), ), - ), - ) - ], - ); + ) + ], + ); + } } } \ No newline at end of file diff --git a/lib/widgets/process_dialog.dart b/lib/widgets/process_dialog.dart index 84c1185..d8794d8 100644 --- a/lib/widgets/process_dialog.dart +++ b/lib/widgets/process_dialog.dart @@ -20,6 +20,7 @@ class ProcessDialog extends StatelessWidget { horizontal: 30 ), child: Row( + mainAxisSize: MainAxisSize.min, children: [ const CircularProgressIndicator(), const SizedBox(width: 40), diff --git a/lib/widgets/servers_list/servers_list.dart b/lib/widgets/servers_list/servers_list.dart index c69f5ed..d4cd9cf 100644 --- a/lib/widgets/servers_list/servers_list.dart +++ b/lib/widgets/servers_list/servers_list.dart @@ -5,6 +5,7 @@ import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:adguard_home_manager/widgets/servers_list/servers_list_item.dart'; +import 'package:adguard_home_manager/widgets/servers_list/servers_tile_item.dart'; import 'package:adguard_home_manager/providers/servers_provider.dart'; @@ -13,6 +14,7 @@ class ServersList extends StatelessWidget { final List controllers; final Function(int) onChange; final ScrollController scrollController; + final double breakingWidth; const ServersList({ Key? key, @@ -20,23 +22,44 @@ class ServersList extends StatelessWidget { required this.controllers, required this.onChange, required this.scrollController, + required this.breakingWidth }) : super(key: key); @override Widget build(BuildContext context) { final serversProvider = Provider.of(context); + + final width = MediaQuery.of(context).size.width; if (serversProvider.serversList.isNotEmpty) { - return ListView.builder( - controller: scrollController, - itemCount: serversProvider.serversList.length, - itemBuilder: (context, index) => ServersListItem( - expandableController: controllers[index], - server: serversProvider.serversList[index], - index: index, - onChange: onChange - ) - ); + if (width > breakingWidth) { + return ListView( + children: [ + Wrap( + children: serversProvider.serversList.asMap().entries.map( + (s) => ServersTileItem( + server: serversProvider.serversList[s.key], + index: s.key, + onChange: onChange + ) + ).toList(), + ), + const SizedBox(height: 8) + ], + ); + } + else { + return ListView.builder( + controller: scrollController, + itemCount: serversProvider.serversList.length, + itemBuilder: (context, index) => ServersListItem( + expandableController: controllers[index], + server: serversProvider.serversList[index], + index: index, + onChange: onChange + ) + ); + } } else { return SizedBox( diff --git a/lib/widgets/servers_list/servers_list_item.dart b/lib/widgets/servers_list/servers_list_item.dart index 919a680..20b9551 100644 --- a/lib/widgets/servers_list/servers_list_item.dart +++ b/lib/widgets/servers_list/servers_list_item.dart @@ -71,6 +71,8 @@ class _ServersListItemState extends State with SingleTickerProv final serversProvider = Provider.of(context); final appConfigProvider = Provider.of(context); + final width = MediaQuery.of(context).size.width; + void showDeleteModal(Server server) async { await Future.delayed(const Duration(seconds: 0), () => { showDialog( @@ -85,10 +87,25 @@ class _ServersListItemState extends State with SingleTickerProv void openAddServerBottomSheet({Server? server}) async { await Future.delayed(const Duration(seconds: 0), (() => { - Navigator.push(context, MaterialPageRoute( - fullscreenDialog: true, - builder: (BuildContext context) => AddServerModal(server: server) - )) + if (width > 700) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => AddServerModal( + server: server, + window: true, + ), + ) + } + else { + Navigator.push(context, MaterialPageRoute( + fullscreenDialog: true, + builder: (BuildContext context) => AddServerModal( + server: server, + window: false, + ) + )) + } })); } @@ -356,7 +373,6 @@ class _ServersListItemState extends State with SingleTickerProv ); } - return Container( decoration: BoxDecoration( border: Border( diff --git a/lib/widgets/servers_list/servers_tile_item.dart b/lib/widgets/servers_list/servers_tile_item.dart new file mode 100644 index 0000000..bf67b6f --- /dev/null +++ b/lib/widgets/servers_list/servers_tile_item.dart @@ -0,0 +1,381 @@ +// ignore_for_file: use_build_context_synchronously + +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/add_server_modal.dart'; +import 'package:adguard_home_manager/widgets/servers_list/delete_modal.dart'; + +import 'package:adguard_home_manager/classes/process_modal.dart'; +import 'package:adguard_home_manager/functions/snackbar.dart'; +import 'package:adguard_home_manager/models/app_log.dart'; +import 'package:adguard_home_manager/providers/app_config_provider.dart'; +import 'package:adguard_home_manager/services/http_requests.dart'; +import 'package:adguard_home_manager/models/server.dart'; +import 'package:adguard_home_manager/providers/servers_provider.dart'; + +class ServersTileItem extends StatefulWidget { + final Server server; + final int index; + final void Function(int) onChange; + + const ServersTileItem({ + Key? key, + required this.server, + required this.index, + required this.onChange + }) : super(key: key); + + @override + State createState() => _ServersTileItemState(); +} + +class _ServersTileItemState extends State with SingleTickerProviderStateMixin { + @override + Widget build(BuildContext context) { + final serversProvider = Provider.of(context); + final appConfigProvider = Provider.of(context); + + final width = MediaQuery.of(context).size.width; + + void showDeleteModal(Server server) async { + await Future.delayed(const Duration(seconds: 0), () => { + showDialog( + context: context, + builder: (context) => DeleteModal( + serverToDelete: server, + ), + barrierDismissible: false + ) + }); + } + + void openAddServerBottomSheet({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, + ), + ) + } + else { + Navigator.push(context, MaterialPageRoute( + fullscreenDialog: true, + builder: (BuildContext context) => AddServerModal( + server: server, + window: false, + ) + )) + } + })); + } + + void connectToServer(Server server) async { + final ProcessModal process = ProcessModal(context: context); + process.open(AppLocalizations.of(context)!.connecting); + + final result = server.runningOnHa == true + ? await loginHA(server) + : await login(server); + + if (result['result'] == 'success') { + serversProvider.setSelectedServer(server); + + serversProvider.setServerStatusLoad(0); + final serverStatus = await getServerStatus(server); + if (serverStatus['result'] == 'success') { + serversProvider.setServerStatusData(serverStatus['data']); + serversProvider.checkServerUpdatesAvailable(server); + serversProvider.setServerStatusLoad(1); + } + else { + appConfigProvider.addLog(serverStatus['log']); + serversProvider.setServerStatusLoad(2); + } + + process.close(); + } + else { + process.close(); + appConfigProvider.addLog(result['log']); + showSnacbkar( + context: context, + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.cannotConnect, + color: Colors.red + ); + } + } + + void setDefaultServer(Server server) async { + final result = await serversProvider.setDefaultServer(server); + if (result == null) { + showSnacbkar( + context: context, + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.connectionDefaultSuccessfully, + color: Colors.green + ); + } + else { + appConfigProvider.addLog( + AppLog( + type: 'set_default_server', + dateTime: DateTime.now(), + message: result.toString() + ) + ); + showSnacbkar( + context: context, + appConfigProvider: appConfigProvider, + label: AppLocalizations.of(context)!.connectionDefaultFailed, + color: Colors.red + ); + } + } + + Widget leadingIcon(Server server) { + if (server.defaultServer == true) { + return Stack( + alignment: Alignment.center, + children: [ + Icon( + Icons.storage_rounded, + color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id + ? serversProvider.serverStatus.data != null + ? Colors.green + : Colors.orange + : null, + ), + SizedBox( + width: 25, + height: 25, + child: Stack( + alignment: Alignment.bottomRight, + children: [ + Container( + padding: const EdgeInsets.all(1), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(20) + ), + child: Icon( + Icons.star, + color: Theme.of(context).colorScheme.onPrimaryContainer, + size: 10, + ), + ), + ], + ), + ) + ], + ); + } + else { + return Icon( + Icons.storage_rounded, + color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id + ? serversProvider.serverStatus.data != null + ? Colors.green + : Colors.orange + : null, + ); + } + } + + Widget topRow(Server server, int index) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + Container( + margin: const EdgeInsets.only(right: 16), + child: leadingIcon(server), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${server.connectionMethod}://${server.domain}${server.path ?? ""}${server.port != null ? ':${server.port}' : ""}", + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurface + ), + ), + Column( + children: [ + const SizedBox(height: 3), + Text( + server.name, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + ) + ], + ) + ], + ), + ), + ], + ), + ), + ], + ); + } + + Widget bottomRow(Server server, int index) { + return Column( + children: [ + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + PopupMenuButton( + // color: Theme.of(context).dialogBackgroundColor, + itemBuilder: (context) => [ + PopupMenuItem( + enabled: server.defaultServer == false + ? true + : false, + onTap: server.defaultServer == false + ? (() => setDefaultServer(server)) + : null, + child: SizedBox( + child: Row( + children: [ + const Icon(Icons.star), + const SizedBox(width: 15), + Text( + server.defaultServer == true + ? AppLocalizations.of(context)!.defaultConnection + : AppLocalizations.of(context)!.setDefault, + ) + ], + ), + ) + ), + PopupMenuItem( + onTap: (() => openAddServerBottomSheet(server: server)), + child: Row( + children: [ + const Icon(Icons.edit), + const SizedBox(width: 15), + Text(AppLocalizations.of(context)!.edit) + ], + ) + ), + PopupMenuItem( + onTap: (() => showDeleteModal(server)), + child: Row( + children: [ + const Icon(Icons.delete), + const SizedBox(width: 15), + Text(AppLocalizations.of(context)!.delete) + ], + ) + ), + ] + ), + SizedBox( + child: serversProvider.selectedServer != null && + serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && serversProvider.serverStatus.data != null && + serversProvider.selectedServer?.id == server.id + ? Container( + margin: const EdgeInsets.only(right: 12), + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), + decoration: BoxDecoration( + color: serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && serversProvider.serverStatus.data != null + ? Colors.green + : Colors.orange, + borderRadius: BorderRadius.circular(30) + ), + child: Row( + children: [ + Icon( + serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && serversProvider.serverStatus.data != null + ? Icons.check + : Icons.warning, + color: Colors.white, + ), + const SizedBox(width: 10), + Text( + serversProvider.selectedServer != null && serversProvider.selectedServer?.id == server.id && serversProvider.serverStatus.data != null + ? AppLocalizations.of(context)!.connected + : AppLocalizations.of(context)!.selectedDisconnected, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500 + ), + ) + ], + ), + ) + : Container( + margin: const EdgeInsets.only(right: 10), + child: TextButton( + onPressed: () => connectToServer(server), + child: Text(AppLocalizations.of(context)!.connect), + ), + ), + ) + ], + ) + ], + ); + } + + EdgeInsets generateMargins(int index) { + if (index == 0) { + return const EdgeInsets.only(top: 16, left: 16, right: 8, bottom: 8); + } + if (index == 1) { + return const EdgeInsets.only(top: 16, left: 8, right: 16, bottom: 8); + } + else if (index == serversProvider.serversList.length-1 && (index+1)%2 == 0) { + return const EdgeInsets.only(top: 8, left: 8, right: 16, bottom: 16); + } + else if (index == serversProvider.serversList.length-1 && (index+1)%2 == 1) { + return const EdgeInsets.only(top: 8, left: 16, right: 8, bottom: 16); + } + else { + if ((index+1)%2 == 0) { + return const EdgeInsets.only(top: 8, left: 8, right: 16, bottom: 8); + } + else { + return const EdgeInsets.only(top: 8, left: 16, right: 8, bottom: 8); + } + } + } + + return FractionallySizedBox( + widthFactor: 0.5, + child: Card( + margin: generateMargins(widget.index), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + topRow(widget.server, widget.index), + bottomRow(widget.server, widget.index) + ], + ), + ), + ), + ); + } +} \ No newline at end of file