diff --git a/.gitignore b/.gitignore index 41ab9c504..d1e9666bd 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,6 @@ flatpak/ccache/** flatpak/.flatpak-builder/build/** flatpak/.flatpak-builder/shared-modules/** flatpak/.flatpak-builder/shared-modules/*.tar.xz -flatpak/.flatpak-builder/debian-binary \ No newline at end of file +flatpak/.flatpak-builder/debian-binary +# bridge file +lib/generated_bridge.dart diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 6166f8121..a708a0a15 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -17,6 +17,8 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:window_manager/window_manager.dart'; import 'common/widgets/overlay.dart'; +import 'mobile/pages/file_manager_page.dart'; +import 'mobile/pages/remote_page.dart'; import 'models/model.dart'; import 'models/platform_model.dart'; @@ -76,59 +78,22 @@ class IconFont { class ColorThemeExtension extends ThemeExtension { const ColorThemeExtension({ - required this.bg, - required this.grayBg, - required this.text, - required this.lightText, - required this.lighterText, - required this.placeholder, required this.border, }); - final Color? bg; - final Color? grayBg; - final Color? text; - final Color? lightText; - final Color? lighterText; - final Color? placeholder; final Color? border; static const light = ColorThemeExtension( - bg: Color(0xFFFFFFFF), - grayBg: Color(0xFFEEEEEE), - text: Color(0xFF222222), - lightText: Color(0xFF666666), - lighterText: Color(0xFF888888), - placeholder: Color(0xFFAAAAAA), border: Color(0xFFCCCCCC), ); static const dark = ColorThemeExtension( - bg: Color(0xFF252525), - grayBg: Color(0xFF141414), - text: Color(0xFFFFFFFF), - lightText: Color(0xFF999999), - lighterText: Color(0xFF777777), - placeholder: Color(0xFF555555), border: Color(0xFF555555), ); @override - ThemeExtension copyWith( - {Color? bg, - Color? grayBg, - Color? text, - Color? lightText, - Color? lighterText, - Color? placeholder, - Color? border}) { + ThemeExtension copyWith({Color? border}) { return ColorThemeExtension( - bg: bg ?? this.bg, - grayBg: grayBg ?? this.grayBg, - text: text ?? this.text, - lightText: lightText ?? this.lightText, - lighterText: lighterText ?? this.lighterText, - placeholder: placeholder ?? this.placeholder, border: border ?? this.border, ); } @@ -140,12 +105,6 @@ class ColorThemeExtension extends ThemeExtension { return this; } return ColorThemeExtension( - bg: Color.lerp(bg, other.bg, t), - grayBg: Color.lerp(grayBg, other.grayBg, t), - text: Color.lerp(text, other.text, t), - lightText: Color.lerp(lightText, other.lightText, t), - lighterText: Color.lerp(lighterText, other.lighterText, t), - placeholder: Color.lerp(placeholder, other.placeholder, t), border: Color.lerp(border, other.border, t), ); } @@ -170,6 +129,14 @@ class MyTheme { static ThemeData lightTheme = ThemeData( brightness: Brightness.light, + backgroundColor: Color(0xFFFFFFFF), + scaffoldBackgroundColor: Color(0xFFEEEEEE), + textTheme: const TextTheme( + titleLarge: TextStyle(fontSize: 19, color: Colors.black87), + bodySmall: TextStyle(fontSize: 12, color: Colors.black54, height: 1.25), + bodyMedium: TextStyle(fontSize: 14, color: Colors.black54, height: 1.25), + ), + hintColor: Color(0xFFAAAAAA), primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, tabBarTheme: const TabBarTheme( @@ -177,6 +144,12 @@ class MyTheme { ), splashColor: Colors.transparent, highlightColor: Colors.transparent, + splashFactory: isDesktop ? NoSplash.splashFactory : null, + textButtonTheme: isDesktop + ? TextButtonThemeData( + style: ButtonStyle(splashFactory: NoSplash.splashFactory), + ) + : null, ).copyWith( extensions: >[ ColorThemeExtension.light, @@ -185,6 +158,13 @@ class MyTheme { ); static ThemeData darkTheme = ThemeData( brightness: Brightness.dark, + backgroundColor: Color(0xFF252525), + scaffoldBackgroundColor: Color(0xFF141414), + textTheme: const TextTheme( + titleLarge: TextStyle(fontSize: 19), + bodySmall: TextStyle(fontSize: 12, height: 1.25), + bodyMedium: TextStyle(fontSize: 14, height: 1.25)), + cardColor: Color(0xFF252525), primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, tabBarTheme: const TabBarTheme( @@ -192,6 +172,12 @@ class MyTheme { ), splashColor: Colors.transparent, highlightColor: Colors.transparent, + splashFactory: isDesktop ? NoSplash.splashFactory : null, + textButtonTheme: isDesktop + ? TextButtonThemeData( + style: ButtonStyle(splashFactory: NoSplash.splashFactory), + ) + : null, ).copyWith( extensions: >[ ColorThemeExtension.dark, @@ -1073,14 +1059,38 @@ void connect(BuildContext context, String id, assert(!(isFileTransfer && isTcpTunneling && isRDP), "more than one connect type"); - FocusScopeNode currentFocus = FocusScope.of(context); - if (isFileTransfer) { - await rustDeskWinManager.newFileTransfer(id); - } else if (isTcpTunneling || isRDP) { - await rustDeskWinManager.newPortForward(id, isRDP); + if (isDesktop) { + if (isFileTransfer) { + await rustDeskWinManager.newFileTransfer(id); + } else if (isTcpTunneling || isRDP) { + await rustDeskWinManager.newPortForward(id, isRDP); + } else { + await rustDeskWinManager.newRemoteDesktop(id); + } } else { - await rustDeskWinManager.newRemoteDesktop(id); + if (isFileTransfer) { + if (!await PermissionManager.check("file")) { + if (!await PermissionManager.request("file")) { + return; + } + } + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => FileManagerPage(id: id), + ), + ); + } else { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => RemotePage(id: id), + ), + ); + } } + + FocusScopeNode currentFocus = FocusScope.of(context); if (!currentFocus.hasPrimaryFocus) { currentFocus.unfocus(); } diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index beecf47f2..47a992bd3 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -1,16 +1,18 @@ import 'package:contextmenu/contextmenu.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common/widgets/peer_widget.dart'; +import 'package:flutter_hbb/common/widgets/peers_view.dart'; import 'package:flutter_hbb/models/ab_model.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import '../../common.dart'; import '../../desktop/pages/desktop_home_page.dart'; +import '../../mobile/pages/settings_page.dart'; import '../../models/platform_model.dart'; class AddressBook extends StatefulWidget { - const AddressBook({Key? key}) : super(key: key); + final EdgeInsets? menuPadding; + const AddressBook({Key? key, this.menuPadding}) : super(key: key); @override State createState() { @@ -37,11 +39,16 @@ class _AddressBookState extends State { }); handleLogin() { - loginDialog().then((success) { - if (success) { - setState(() {}); - } - }); + // TODO refactor login dialog for desktop and mobile + if (isDesktop) { + loginDialog().then((success) { + if (success) { + setState(() {}); + } + }); + } else { + showLogin(gFFI.dialogManager); + } } Future buildAddressBook(BuildContext context) async { @@ -108,7 +115,8 @@ class _AddressBookState extends State { Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), - side: const BorderSide(color: MyTheme.grayBg)), + side: BorderSide( + color: Theme.of(context).scaffoldBackgroundColor)), child: Container( width: 200, height: double.infinity, @@ -174,7 +182,9 @@ class _AddressBookState extends State { Expanded( child: Align( alignment: Alignment.topLeft, - child: AddressBookPeerWidget()), + child: AddressBookPeersView( + menuPadding: widget.menuPadding, + )), ) ], )); @@ -206,7 +216,8 @@ class _AddressBookState extends State { child: Text( tagName, style: TextStyle( - color: rxTags.contains(tagName) ? MyTheme.white : null), + color: + rxTags.contains(tagName) ? Colors.white : null), // TODO ), ), ), diff --git a/flutter/lib/common/widgets/dialog.dart b/flutter/lib/common/widgets/dialog.dart index 82e4fb5cc..cfb57a4cb 100644 --- a/flutter/lib/common/widgets/dialog.dart +++ b/flutter/lib/common/widgets/dialog.dart @@ -72,3 +72,86 @@ void changeIdDialog() { ); }); } + +void changeWhiteList({Function()? callback}) async { + var newWhiteList = (await bind.mainGetOption(key: 'whitelist')).split(','); + var newWhiteListField = newWhiteList.join('\n'); + var controller = TextEditingController(text: newWhiteListField); + var msg = ""; + var isInProgress = false; + gFFI.dialogManager.show((setState, close) { + return CustomAlertDialog( + title: Text(translate("IP Whitelisting")), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(translate("whitelist_sep")), + const SizedBox( + height: 8.0, + ), + Row( + children: [ + Expanded( + child: TextField( + maxLines: null, + decoration: InputDecoration( + border: const OutlineInputBorder(), + errorText: msg.isEmpty ? null : translate(msg), + ), + controller: controller, + focusNode: FocusNode()..requestFocus()), + ), + ], + ), + const SizedBox( + height: 4.0, + ), + Offstage( + offstage: !isInProgress, child: const LinearProgressIndicator()) + ], + ), + actions: [ + TextButton(onPressed: close, child: Text(translate("Cancel"))), + TextButton( + onPressed: () async { + await bind.mainSetOption(key: 'whitelist', value: ''); + callback?.call(); + close(); + }, + child: Text(translate("Clear"))), + TextButton( + onPressed: () async { + setState(() { + msg = ""; + isInProgress = true; + }); + newWhiteListField = controller.text.trim(); + var newWhiteList = ""; + if (newWhiteListField.isEmpty) { + // pass + } else { + final ips = + newWhiteListField.trim().split(RegExp(r"[\s,;\n]+")); + // test ip + final ipMatch = RegExp(r"^\d+\.\d+\.\d+\.\d+$"); + for (final ip in ips) { + if (!ipMatch.hasMatch(ip)) { + msg = "${translate("Invalid IP")} $ip"; + setState(() { + isInProgress = false; + }); + return; + } + } + newWhiteList = ips.join(','); + } + await bind.mainSetOption(key: 'whitelist', value: newWhiteList); + callback?.call(); + close(); + }, + child: Text(translate("OK"))), + ], + onCancel: close, + ); + }); +} diff --git a/flutter/lib/common/widgets/overlay.dart b/flutter/lib/common/widgets/overlay.dart index 0e0a6ce2d..89dbe2ea6 100644 --- a/flutter/lib/common/widgets/overlay.dart +++ b/flutter/lib/common/widgets/overlay.dart @@ -26,7 +26,7 @@ class DraggableChatWindow extends StatelessWidget { position: position, width: width, height: height, - builder: (_, onPanUpdate) { + builder: (context, onPanUpdate) { return isIOS ? ChatPage(chatModel: chatModel) : Scaffold( @@ -35,16 +35,16 @@ class DraggableChatWindow extends StatelessWidget { onPanUpdate: onPanUpdate, appBar: isDesktop ? _buildDesktopAppBar() - : _buildMobileAppBar(), + : _buildMobileAppBar(context), ), body: ChatPage(chatModel: chatModel), ); }); } - Widget _buildMobileAppBar() { + Widget _buildMobileAppBar(BuildContext context) { return Container( - color: MyTheme.accent50, + color: Theme.of(context).colorScheme.primary, height: 50, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -169,17 +169,17 @@ class DraggableMobileActions extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ IconButton( - color: MyTheme.white, + color: Colors.white, onPressed: onBackPressed, splashRadius: 20, icon: const Icon(Icons.arrow_back)), IconButton( - color: MyTheme.white, + color: Colors.white, onPressed: onHomePressed, splashRadius: 20, icon: const Icon(Icons.home)), IconButton( - color: MyTheme.white, + color: Colors.white, onPressed: onRecentPressed, splashRadius: 20, icon: const Icon(Icons.more_horiz)), @@ -190,7 +190,7 @@ class DraggableMobileActions extends StatelessWidget { endIndent: 10, ), IconButton( - color: MyTheme.white, + color: Colors.white, onPressed: onHidePressed, splashRadius: 20, icon: const Icon(Icons.keyboard_arrow_down)), diff --git a/flutter/lib/common/widgets/peercard_widget.dart b/flutter/lib/common/widgets/peer_card.dart similarity index 81% rename from flutter/lib/common/widgets/peercard_widget.dart rename to flutter/lib/common/widgets/peer_card.dart index ecf89283a..9a77eee04 100644 --- a/flutter/lib/common/widgets/peercard_widget.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -1,6 +1,7 @@ import 'package:contextmenu/contextmenu.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_hbb/consts.dart'; import 'package:get/get.dart'; import '../../common.dart'; @@ -14,7 +15,7 @@ import '../../desktop/widgets/popup_menu.dart'; class _PopupMenuTheme { static const Color commonColor = MyTheme.accent; // kMinInteractiveDimension - static const double height = 25.0; + static const double height = 20.0; static const double dividerHeight = 3.0; } @@ -73,12 +74,18 @@ class _PeerCardState extends State<_PeerCard> _showPeerMenu(peer.id); }, child: ListTile( - contentPadding: const EdgeInsets.only(left: 12), + contentPadding: const EdgeInsets.only(left: 12), // subtitle: Text('${peer.username}@${peer.hostname}'), - title: Text(peer.alias.isEmpty ? formatID(peer.id) : peer.alias), + title: Row(children: [ + getOnline(4, peer.online), + Text(peer.alias.isEmpty ? formatID(peer.id) : peer.alias) + ]), leading: Container( + decoration: BoxDecoration( + color: str2color('${peer.id}${peer.platform}', 0x7f), + borderRadius: BorderRadius.circular(4), + ), padding: const EdgeInsets.all(6), - color: str2color('${peer.id}${peer.platform}', 0x7f), child: getPlatformImage(peer.platform)), trailing: InkWell( child: const Padding( @@ -105,7 +112,9 @@ class _PeerCardState extends State<_PeerCard> return MouseRegion( onEnter: (evt) { deco.value = BoxDecoration( - border: Border.all(color: MyTheme.button, width: _borderWidth), + border: Border.all( + color: Theme.of(context).colorScheme.secondary, + width: _borderWidth), borderRadius: peerCardUiType.value == PeerUiType.grid ? BorderRadius.circular(_cardRadis) : null); @@ -127,8 +136,10 @@ class _PeerCardState extends State<_PeerCard> Widget _buildPeerTile( BuildContext context, Peer peer, Rx deco) { - final greyStyle = - TextStyle(fontSize: 12, color: MyTheme.color(context).lighterText); + final greyStyle = TextStyle( + fontSize: 11, + color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6)); + final alias = bind.mainGetPeerOptionSync(id: peer.id, key: 'alias'); return Obx( () => Container( foregroundDecoration: deco.value, @@ -144,66 +155,36 @@ class _PeerCardState extends State<_PeerCard> ), Expanded( child: Container( - decoration: BoxDecoration(color: MyTheme.color(context).bg), + decoration: + BoxDecoration(color: Theme.of(context).backgroundColor), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Row(children: [ - Padding( - padding: const EdgeInsets.fromLTRB(0, 4, 4, 4), - child: CircleAvatar( - radius: 5, - backgroundColor: peer.online - ? Colors.green - : Colors.yellow)), - Text( - formatID(peer.id), - style: - const TextStyle(fontWeight: FontWeight.w400), - ), - ]), + getOnline(8, peer.online), + Expanded( + child: Text( + alias.isEmpty ? formatID(peer.id) : alias, + overflow: TextOverflow.ellipsis, + )), + ]).marginOnly(bottom: 2), Align( alignment: Alignment.centerLeft, - child: FutureBuilder( - future: bind.mainGetPeerOption( - id: peer.id, key: 'alias'), - builder: (_, snapshot) { - if (snapshot.hasData) { - final name = snapshot.data!.isEmpty - ? '${peer.username}@${peer.hostname}' - : snapshot.data!; - return Tooltip( - message: name, - waitDuration: const Duration(seconds: 1), - child: Text( - name, - style: greyStyle, - textAlign: TextAlign.start, - overflow: TextOverflow.ellipsis, - ), - ); - } else { - // alias has not arrived - return Text( - '${peer.username}@${peer.hostname}', - style: greyStyle, - textAlign: TextAlign.start, - overflow: TextOverflow.ellipsis, - ); - } - }, + child: Text( + '${peer.username}@${peer.hostname}', + style: greyStyle, + textAlign: TextAlign.start, + overflow: TextOverflow.ellipsis, ), ), ], - ), + ).marginOnly(top: 2), ), _actionMore(peer), ], - ).paddingSymmetric(horizontal: 4.0), + ).paddingOnly(left: 10.0, top: 3.0), ), ) ], @@ -268,21 +249,19 @@ class _PeerCardState extends State<_PeerCard> ), ), Container( - color: MyTheme.color(context).bg, + color: Theme.of(context).backgroundColor, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row(children: [ - Padding( - padding: const EdgeInsets.fromLTRB(0, 4, 8, 4), - child: CircleAvatar( - radius: 5, - backgroundColor: peer.online - ? Colors.green - : Colors.yellow)), - Text( - peer.alias.isEmpty ? formatID(peer.id) : peer.alias) - ]).paddingSymmetric(vertical: 8), + Expanded( + child: Row(children: [ + getOnline(8, peer.online), + Expanded( + child: Text( + peer.alias.isEmpty ? formatID(peer.id) : peer.alias, + overflow: TextOverflow.ellipsis, + )), + ]).paddingSymmetric(vertical: 8)), _actionMore(peer), ], ).paddingSymmetric(horizontal: 12.0), @@ -308,13 +287,21 @@ class _PeerCardState extends State<_PeerCard> child: CircleAvatar( radius: 14, backgroundColor: _iconMoreHover.value - ? MyTheme.color(context).grayBg! - : MyTheme.color(context).bg!, + ? Theme.of(context).scaffoldBackgroundColor + : Theme.of(context).backgroundColor, + // ? Theme.of(context).scaffoldBackgroundColor! + // : Theme.of(context).backgroundColor!, child: Icon(Icons.more_vert, size: 18, color: _iconMoreHover.value - ? MyTheme.color(context).text - : MyTheme.color(context).lightText)))); + ? Theme.of(context).textTheme.titleLarge?.color + : Theme.of(context) + .textTheme + .titleLarge + ?.color + ?.withOpacity(0.5))))); + // ? MyTheme.color(context).text + // : MyTheme.color(context).lightText)))); /// Show the peer menu and handle user's choice. /// User might remove the peer or send a file to the peer. @@ -333,8 +320,10 @@ class _PeerCardState extends State<_PeerCard> abstract class BasePeerCard extends StatelessWidget { final Peer peer; + final EdgeInsets? menuPadding; - BasePeerCard({required this.peer, Key? key}) : super(key: key); + BasePeerCard({required this.peer, this.menuPadding, Key? key}) + : super(key: key); @override Widget build(BuildContext context) { @@ -379,6 +368,7 @@ abstract class BasePeerCard extends StatelessWidget { isRDP: isRDP, ); }, + padding: menuPadding, dismissOnClicked: true, ); } @@ -428,17 +418,25 @@ abstract class BasePeerCard extends StatelessWidget { Expanded( child: Align( alignment: Alignment.centerRight, - child: IconButton( - padding: EdgeInsets.zero, - icon: const Icon(Icons.edit), - onPressed: () => _rdpDialog(id), - ), + child: Transform.scale( + scale: 0.8, + child: IconButton( + icon: const Icon(Icons.edit), + padding: EdgeInsets.zero, + onPressed: () { + if (Navigator.canPop(context)) { + Navigator.pop(context); + } + _rdpDialog(id); + }, + )), )) ], )), proc: () { connect(context, id, isRDP: true); }, + padding: menuPadding, dismissOnClicked: true, ); } @@ -453,6 +451,7 @@ abstract class BasePeerCard extends StatelessWidget { proc: () { bind.mainWol(id: id); }, + padding: menuPadding, dismissOnClicked: true, ); } @@ -461,6 +460,7 @@ abstract class BasePeerCard extends StatelessWidget { Future> _forceAlwaysRelayAction(String id) async { const option = 'force-always-relay'; return MenuEntrySwitch( + switchType: SwitchType.scheckbox, text: translate('Always connect via relay'), getter: () async { return (await bind.mainGetPeerOption(id: id, key: option)).isNotEmpty; @@ -475,6 +475,7 @@ abstract class BasePeerCard extends StatelessWidget { } await bind.mainSetPeerOption(id: id, key: option, value: value); }, + padding: menuPadding, dismissOnClicked: true, ); } @@ -489,13 +490,15 @@ abstract class BasePeerCard extends StatelessWidget { proc: () { _rename(id, isAddressBook); }, + padding: menuPadding, dismissOnClicked: true, ); } @protected MenuEntryBase _removeAction( - String id, Future Function() reloadFunc) { + String id, Future Function() reloadFunc, + {bool isLan = false}) { return MenuEntryButton( childBuilder: (TextStyle? style) => Text( translate('Remove'), @@ -503,12 +506,16 @@ abstract class BasePeerCard extends StatelessWidget { ), proc: () { () async { - await bind.mainRemovePeer(id: id); + if (isLan) { + // TODO + } else { + await bind.mainRemovePeer(id: id); + } removePreference(id); await reloadFunc(); - // Get.forceAppUpdate(); // TODO use inner model / state }(); }, + padding: menuPadding, dismissOnClicked: true, ); } @@ -523,6 +530,7 @@ abstract class BasePeerCard extends StatelessWidget { proc: () { bind.mainForgetPassword(id: id); }, + padding: menuPadding, dismissOnClicked: true, ); } @@ -543,6 +551,7 @@ abstract class BasePeerCard extends StatelessWidget { } }(); }, + padding: menuPadding, dismissOnClicked: true, ); } @@ -561,10 +570,10 @@ abstract class BasePeerCard extends StatelessWidget { if (favs.remove(id)) { await bind.mainStoreFav(favs: favs); await reloadFunc(); - // Get.forceAppUpdate(); // TODO use inner model / state } }(); }, + padding: menuPadding, dismissOnClicked: true, ); } @@ -606,8 +615,6 @@ abstract class BasePeerCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: - const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: Form( child: TextFormField( controller: controller, @@ -634,7 +641,8 @@ abstract class BasePeerCard extends StatelessWidget { } class RecentPeerCard extends BasePeerCard { - RecentPeerCard({required Peer peer, Key? key}) : super(peer: peer, key: key); + RecentPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) + : super(peer: peer, menuPadding: menuPadding, key: key); @override Future>> _buildMenuItems( @@ -642,15 +650,13 @@ class RecentPeerCard extends BasePeerCard { final List> menuItems = [ _connectAction(context, peer), _transferFileAction(context, peer.id), - _tcpTunnelingAction(context, peer.id), ]; - MenuEntryBase? rdpAction; - if (peer.platform == 'Windows') { - rdpAction = _rdpAction(context, peer.id); + if (isDesktop) { + menuItems.add(_tcpTunnelingAction(context, peer.id)); } menuItems.add(await _forceAlwaysRelayAction(peer.id)); - if (rdpAction != null) { - menuItems.add(rdpAction); + if (peer.platform == 'Windows') { + menuItems.add(_rdpAction(context, peer.id)); } menuItems.add(_wolAction(peer.id)); menuItems.add(MenuEntryDivider()); @@ -658,15 +664,17 @@ class RecentPeerCard extends BasePeerCard { menuItems.add(_removeAction(peer.id, () async { await bind.mainLoadRecentPeers(); })); - menuItems.add(_unrememberPasswordAction(peer.id)); + if (await bind.mainPeerHasPassword(id: peer.id)) { + menuItems.add(_unrememberPasswordAction(peer.id)); + } menuItems.add(_addFavAction(peer.id)); return menuItems; } } class FavoritePeerCard extends BasePeerCard { - FavoritePeerCard({required Peer peer, Key? key}) - : super(peer: peer, key: key); + FavoritePeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) + : super(peer: peer, menuPadding: menuPadding, key: key); @override Future>> _buildMenuItems( @@ -674,15 +682,13 @@ class FavoritePeerCard extends BasePeerCard { final List> menuItems = [ _connectAction(context, peer), _transferFileAction(context, peer.id), - _tcpTunnelingAction(context, peer.id), ]; - MenuEntryBase? rdpAction; - if (peer.platform == 'Windows') { - rdpAction = _rdpAction(context, peer.id); + if (isDesktop) { + menuItems.add(_tcpTunnelingAction(context, peer.id)); } menuItems.add(await _forceAlwaysRelayAction(peer.id)); - if (rdpAction != null) { - menuItems.add(rdpAction); + if (peer.platform == 'Windows') { + menuItems.add(_rdpAction(context, peer.id)); } menuItems.add(_wolAction(peer.id)); menuItems.add(MenuEntryDivider()); @@ -690,7 +696,9 @@ class FavoritePeerCard extends BasePeerCard { menuItems.add(_removeAction(peer.id, () async { await bind.mainLoadFavPeers(); })); - menuItems.add(_unrememberPasswordAction(peer.id)); + if (await bind.mainPeerHasPassword(id: peer.id)) { + menuItems.add(_unrememberPasswordAction(peer.id)); + } menuItems.add(_rmFavAction(peer.id, () async { await bind.mainLoadFavPeers(); })); @@ -699,8 +707,8 @@ class FavoritePeerCard extends BasePeerCard { } class DiscoveredPeerCard extends BasePeerCard { - DiscoveredPeerCard({required Peer peer, Key? key}) - : super(peer: peer, key: key); + DiscoveredPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) + : super(peer: peer, menuPadding: menuPadding, key: key); @override Future>> _buildMenuItems( @@ -708,30 +716,24 @@ class DiscoveredPeerCard extends BasePeerCard { final List> menuItems = [ _connectAction(context, peer), _transferFileAction(context, peer.id), - _tcpTunnelingAction(context, peer.id), ]; - MenuEntryBase? rdpAction; - if (peer.platform == 'Windows') { - rdpAction = _rdpAction(context, peer.id); + if (isDesktop) { + menuItems.add(_tcpTunnelingAction(context, peer.id)); } menuItems.add(await _forceAlwaysRelayAction(peer.id)); - if (rdpAction != null) { - menuItems.add(rdpAction); + if (peer.platform == 'Windows') { + menuItems.add(_rdpAction(context, peer.id)); } menuItems.add(_wolAction(peer.id)); menuItems.add(MenuEntryDivider()); - menuItems.add(_renameAction(peer.id, false)); - menuItems.add(_removeAction(peer.id, () async { - await bind.mainLoadLanPeers(); - })); - menuItems.add(_unrememberPasswordAction(peer.id)); + menuItems.add(_removeAction(peer.id, () async {})); return menuItems; } } class AddressBookPeerCard extends BasePeerCard { - AddressBookPeerCard({required Peer peer, Key? key}) - : super(peer: peer, key: key); + AddressBookPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) + : super(peer: peer, menuPadding: menuPadding, key: key); @override Future>> _buildMenuItems( @@ -739,22 +741,21 @@ class AddressBookPeerCard extends BasePeerCard { final List> menuItems = [ _connectAction(context, peer), _transferFileAction(context, peer.id), - _tcpTunnelingAction(context, peer.id), ]; - MenuEntryBase? rdpAction; - if (peer.platform == 'Windows') { - rdpAction = _rdpAction(context, peer.id); + if (isDesktop) { + menuItems.add(_tcpTunnelingAction(context, peer.id)); } menuItems.add(await _forceAlwaysRelayAction(peer.id)); - if (rdpAction != null) { - menuItems.add(rdpAction); + if (peer.platform == 'Windows') { + menuItems.add(_rdpAction(context, peer.id)); } menuItems.add(_wolAction(peer.id)); menuItems.add(MenuEntryDivider()); menuItems.add(_renameAction(peer.id, false)); menuItems.add(_removeAction(peer.id, () async {})); - menuItems.add(_unrememberPasswordAction(peer.id)); - menuItems.add(_addFavAction(peer.id)); + if (await bind.mainPeerHasPassword(id: peer.id)) { + menuItems.add(_unrememberPasswordAction(peer.id)); + } menuItems.add(_editTagAction(peer.id)); return menuItems; } @@ -762,7 +763,8 @@ class AddressBookPeerCard extends BasePeerCard { @protected @override MenuEntryBase _removeAction( - String id, Future Function() reloadFunc) { + String id, Future Function() reloadFunc, + {bool isLan = false}) { return MenuEntryButton( childBuilder: (TextStyle? style) => Text( translate('Remove'), @@ -774,6 +776,7 @@ class AddressBookPeerCard extends BasePeerCard { await gFFI.abModel.updateAb(); }(); }, + padding: super.menuPadding, dismissOnClicked: true, ); } @@ -788,6 +791,7 @@ class AddressBookPeerCard extends BasePeerCard { proc: () { _abEditTag(id); }, + padding: super.menuPadding, dismissOnClicked: true, ); } @@ -869,7 +873,7 @@ class AddressBookPeerCard extends BasePeerCard { child: Text( tagName, style: TextStyle( - color: rxTags.contains(tagName) ? MyTheme.white : null), + color: rxTags.contains(tagName) ? Colors.white : null), ), ), ), @@ -995,3 +999,13 @@ void _rdpDialog(String id) async { ); }); } + +Widget getOnline(double rightPadding, bool online) { + return Tooltip( + message: translate(online ? 'Online' : 'Offline'), + waitDuration: const Duration(seconds: 1), + child: Padding( + padding: EdgeInsets.fromLTRB(0, 4, rightPadding, 4), + child: CircleAvatar( + radius: 3, backgroundColor: online ? Colors.green : kColorWarn))); +} diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index fefe74671..64f353130 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common/widgets/peer_widget.dart'; -import 'package:flutter_hbb/common/widgets/peercard_widget.dart'; +import 'package:flutter_hbb/common/widgets/peers_view.dart'; +import 'package:flutter_hbb/common/widgets/peer_card.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:get/get.dart'; @@ -42,9 +42,6 @@ class _PeerTabPageState extends State // hard code for now Future _handleTabSelection(int index) async { - // reset search text - peerSearchText.value = ""; - peerSearchTextController.clear(); _tabIndex.value = index; await bind.mainSetLocalOption( key: 'peer-tab-index', value: index.toString()); @@ -101,6 +98,7 @@ class _PeerTabPageState extends State } Widget _createTabBar(BuildContext context) { + final textColor = Theme.of(context).textTheme.titleLarge?.color; return ListView( scrollDirection: Axis.horizontal, shrinkWrap: true, @@ -111,9 +109,9 @@ class _PeerTabPageState extends State padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( color: _tabIndex.value == t.key - ? MyTheme.color(context).bg + ? Theme.of(context).backgroundColor : null, - borderRadius: BorderRadius.circular(2), + borderRadius: BorderRadius.circular(isDesktop ? 2 : 6), ), child: Align( alignment: Alignment.center, @@ -123,9 +121,9 @@ class _PeerTabPageState extends State style: TextStyle( height: 1, fontSize: 14, - color: _tabIndex.value == t.key - ? MyTheme.color(context).text - : MyTheme.color(context).lightText), + color: + _tabIndex.value == t.key ? textColor : textColor + ?..withOpacity(0.5)), ), )), onTap: () async => await _handleTabSelection(t.key), @@ -147,7 +145,8 @@ class _PeerTabPageState extends State } Widget _createPeerViewTypeSwitch(BuildContext context) { - final activeDeco = BoxDecoration(color: MyTheme.color(context).bg); + final textColor = Theme.of(context).textTheme.titleLarge?.color; + final activeDeco = BoxDecoration(color: Theme.of(context).backgroundColor); return Row( children: [PeerUiType.grid, PeerUiType.list] .map((type) => Obx( @@ -166,9 +165,9 @@ class _PeerTabPageState extends State ? Icons.grid_view_rounded : Icons.list, size: 18, - color: peerCardUiType.value == type - ? MyTheme.color(context).text - : MyTheme.color(context).lightText, + color: + peerCardUiType.value == type ? textColor : textColor + ?..withOpacity(0.5), )), ), )) @@ -199,9 +198,9 @@ class _PeerSearchBarState extends State { drawer = true; }); }, - icon: const Icon( + icon: Icon( Icons.search_rounded, - color: MyTheme.dark, + color: Theme.of(context).hintColor, )); } @@ -212,7 +211,7 @@ class _PeerSearchBarState extends State { return Container( width: 120, decoration: BoxDecoration( - color: MyTheme.color(context).bg, + color: Theme.of(context).backgroundColor, borderRadius: BorderRadius.circular(6), ), child: Obx(() => Row( @@ -222,7 +221,7 @@ class _PeerSearchBarState extends State { children: [ Icon( Icons.search_rounded, - color: MyTheme.color(context).placeholder, + color: Theme.of(context).hintColor, ).marginSymmetric(horizontal: 4), Expanded( child: TextField( @@ -234,7 +233,11 @@ class _PeerSearchBarState extends State { focusNode: focusNode, textAlign: TextAlign.start, maxLines: 1, - cursorColor: MyTheme.color(context).lightText, + cursorColor: Theme.of(context) + .textTheme + .titleLarge + ?.color + ?.withOpacity(0.5), cursorHeight: 18, cursorWidth: 1, style: const TextStyle(fontSize: 14), @@ -244,8 +247,7 @@ class _PeerSearchBarState extends State { hintText: focused.value ? null : translate("Search ID"), hintStyle: TextStyle( - fontSize: 14, - color: MyTheme.color(context).placeholder), + fontSize: 14, color: Theme.of(context).hintColor), border: InputBorder.none, isDense: true, ), @@ -262,9 +264,9 @@ class _PeerSearchBarState extends State { drawer = false; }); }, - icon: const Icon( + icon: Icon( Icons.close, - color: MyTheme.dark, + color: Theme.of(context).hintColor, )), ], ), diff --git a/flutter/lib/common/widgets/peer_widget.dart b/flutter/lib/common/widgets/peers_view.dart similarity index 84% rename from flutter/lib/common/widgets/peer_widget.dart rename to flutter/lib/common/widgets/peers_view.dart index e6236ff4e..63c29af6d 100644 --- a/flutter/lib/common/widgets/peer_widget.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -11,34 +11,34 @@ import 'package:window_manager/window_manager.dart'; import '../../common.dart'; import '../../models/peer_model.dart'; import '../../models/platform_model.dart'; -import 'peercard_widget.dart'; +import 'peer_card.dart'; typedef OffstageFunc = bool Function(Peer peer); -typedef PeerCardWidgetFunc = Widget Function(Peer peer); +typedef PeerCardBuilder = BasePeerCard Function(Peer peer); /// for peer search text, global obs value final peerSearchText = "".obs; final peerSearchTextController = TextEditingController(text: peerSearchText.value); -class _PeerWidget extends StatefulWidget { +class _PeersView extends StatefulWidget { final Peers peers; final OffstageFunc offstageFunc; - final PeerCardWidgetFunc peerCardWidgetFunc; + final PeerCardBuilder peerCardBuilder; - const _PeerWidget( + const _PeersView( {required this.peers, required this.offstageFunc, - required this.peerCardWidgetFunc, + required this.peerCardBuilder, Key? key}) : super(key: key); @override - _PeerWidgetState createState() => _PeerWidgetState(); + _PeersViewState createState() => _PeersViewState(); } /// State for the peer widget. -class _PeerWidgetState extends State<_PeerWidget> with WindowListener { +class _PeersViewState extends State<_PeersView> with WindowListener { static const int _maxQueryCount = 3; final space = isDesktop ? 12.0 : 8.0; final _curPeers = {}; @@ -60,7 +60,7 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener { return width; }(); - _PeerWidgetState() { + _PeersViewState() { _startCheckOnlines(); } @@ -119,7 +119,7 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener { } _lastChangeTime = DateTime.now(); }, - child: widget.peerCardWidgetFunc(peer), + child: widget.peerCardBuilder(peer), ); cards.add(Offstage( key: ValueKey("off${peer.id}"), @@ -198,40 +198,41 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener { } } -abstract class BasePeerWidget extends StatelessWidget { +abstract class BasePeersView extends StatelessWidget { final String name; final String loadEvent; final OffstageFunc offstageFunc; - final PeerCardWidgetFunc peerCardWidgetFunc; + final PeerCardBuilder peerCardBuilder; final List initPeers; - const BasePeerWidget({ + const BasePeersView({ Key? key, required this.name, required this.loadEvent, required this.offstageFunc, - required this.peerCardWidgetFunc, + required this.peerCardBuilder, required this.initPeers, }) : super(key: key); @override Widget build(BuildContext context) { - return _PeerWidget( + return _PeersView( peers: Peers(name: name, loadEvent: loadEvent, peers: initPeers), offstageFunc: offstageFunc, - peerCardWidgetFunc: peerCardWidgetFunc); + peerCardBuilder: peerCardBuilder); } } -class RecentPeerWidget extends BasePeerWidget { - RecentPeerWidget({Key? key}) +class RecentPeersView extends BasePeersView { + RecentPeersView({Key? key, EdgeInsets? menuPadding}) : super( key: key, name: 'recent peer', loadEvent: 'load_recent_peers', offstageFunc: (Peer peer) => false, - peerCardWidgetFunc: (Peer peer) => RecentPeerCard( + peerCardBuilder: (Peer peer) => RecentPeerCard( peer: peer, + menuPadding: menuPadding, ), initPeers: [], ); @@ -244,15 +245,16 @@ class RecentPeerWidget extends BasePeerWidget { } } -class FavoritePeerWidget extends BasePeerWidget { - FavoritePeerWidget({Key? key}) +class FavoritePeersView extends BasePeersView { + FavoritePeersView({Key? key, EdgeInsets? menuPadding}) : super( key: key, name: 'favorite peer', loadEvent: 'load_fav_peers', offstageFunc: (Peer peer) => false, - peerCardWidgetFunc: (Peer peer) => FavoritePeerCard( + peerCardBuilder: (Peer peer) => FavoritePeerCard( peer: peer, + menuPadding: menuPadding, ), initPeers: [], ); @@ -265,15 +267,16 @@ class FavoritePeerWidget extends BasePeerWidget { } } -class DiscoveredPeerWidget extends BasePeerWidget { - DiscoveredPeerWidget({Key? key}) +class DiscoveredPeersView extends BasePeersView { + DiscoveredPeersView({Key? key, EdgeInsets? menuPadding}) : super( key: key, name: 'discovered peer', loadEvent: 'load_lan_peers', offstageFunc: (Peer peer) => false, - peerCardWidgetFunc: (Peer peer) => DiscoveredPeerCard( + peerCardBuilder: (Peer peer) => DiscoveredPeerCard( peer: peer, + menuPadding: menuPadding, ), initPeers: [], ); @@ -286,16 +289,17 @@ class DiscoveredPeerWidget extends BasePeerWidget { } } -class AddressBookPeerWidget extends BasePeerWidget { - AddressBookPeerWidget({Key? key}) +class AddressBookPeersView extends BasePeersView { + AddressBookPeersView({Key? key, EdgeInsets? menuPadding}) : super( key: key, name: 'address book peer', loadEvent: 'load_address_book_peers', offstageFunc: (Peer peer) => !_hitTag(gFFI.abModel.selectedTags, peer.tags), - peerCardWidgetFunc: (Peer peer) => AddressBookPeerCard( + peerCardBuilder: (Peer peer) => AddressBookPeerCard( peer: peer, + menuPadding: menuPadding, ), initPeers: _loadPeers(), ); diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index ff15173d3..808c24b7e 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -11,12 +11,16 @@ const String kAppTypeDesktopPortForward = "port forward"; const String kTabLabelHomePage = "Home"; const String kTabLabelSettingPage = "Settings"; +const Color kColorWarn = Color.fromARGB(255, 245, 133, 59); + const int kMobileDefaultDisplayWidth = 720; const int kMobileDefaultDisplayHeight = 1280; const int kDesktopDefaultDisplayWidth = 1080; const int kDesktopDefaultDisplayHeight = 720; +const Size kConnectionManagerWindowSize = Size(300, 400); + /// [kDefaultScrollAmountMultiplier] indicates how many rows can be scrolled after a minimum scroll action of mouse const kDefaultScrollAmountMultiplier = 5.0; const kDefaultScrollDuration = Duration(milliseconds: 50); diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index ad8e430f4..07246a916 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -5,14 +5,16 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/widgets/address_book.dart'; +import 'package:flutter_hbb/consts.dart'; import 'package:get/get.dart'; import 'package:url_launcher/url_launcher_string.dart'; import '../../common.dart'; import '../../common/formatter/id_formatter.dart'; import '../../common/widgets/peer_tab_page.dart'; -import '../../common/widgets/peer_widget.dart'; +import '../../common/widgets/peers_view.dart'; import '../../models/platform_model.dart'; +import '../widgets/button.dart'; /// Connection page for connecting to a remote peer. class ConnectionPage extends StatefulWidget { @@ -74,10 +76,18 @@ class _ConnectionPageState extends State { translate('Address Book') ], children: [ - RecentPeerWidget(), - FavoritePeerWidget(), - DiscoveredPeerWidget(), - const AddressBook(), + RecentPeersView( + menuPadding: EdgeInsets.only(left: 12.0, right: 3.0), + ), + FavoritePeersView( + menuPadding: EdgeInsets.only(left: 12.0, right: 3.0), + ), + DiscoveredPeersView( + menuPadding: EdgeInsets.only(left: 12.0, right: 3.0), + ), + const AddressBook( + menuPadding: EdgeInsets.only(left: 12.0, right: 3.0), + ), ], )), ], @@ -100,10 +110,6 @@ class _ConnectionPageState extends State { /// UI for the remote ID TextField. /// Search for a peer and connect to it if the id exists. Widget _buildRemoteIDTextField(BuildContext context) { - RxBool ftHover = false.obs; - RxBool ftPressed = false.obs; - RxBool connHover = false.obs; - RxBool connPressed = false.obs; RxBool inputFocused = false.obs; FocusNode focusNode = FocusNode(); focusNode.addListener(() { @@ -113,7 +119,7 @@ class _ConnectionPageState extends State { width: 320 + 20 * 2, padding: const EdgeInsets.fromLTRB(20, 24, 20, 22), decoration: BoxDecoration( - color: MyTheme.color(context).bg, + color: Theme.of(context).backgroundColor, borderRadius: const BorderRadius.all(Radius.circular(13)), ), child: Ink( @@ -123,7 +129,10 @@ class _ConnectionPageState extends State { children: [ Text( translate('Control Remote Desktop'), - style: const TextStyle(fontSize: 19, height: 1), + style: Theme.of(context) + .textTheme + .titleLarge + ?.merge(TextStyle(height: 1)), ), ], ).marginOnly(bottom: 15), @@ -142,13 +151,12 @@ class _ConnectionPageState extends State { height: 1, ), maxLines: 1, - cursorColor: MyTheme.color(context).text!, + cursorColor: + Theme.of(context).textTheme.titleLarge?.color, decoration: InputDecoration( hintText: inputFocused.value ? null : translate('Enter Remote ID'), - hintStyle: TextStyle( - color: MyTheme.color(context).placeholder), border: OutlineInputBorder( borderRadius: BorderRadius.zero, borderSide: BorderSide( @@ -180,84 +188,17 @@ class _ConnectionPageState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - Obx(() => InkWell( - onTapDown: (_) => ftPressed.value = true, - onTapUp: (_) => ftPressed.value = false, - onTapCancel: () => ftPressed.value = false, - onHover: (value) => ftHover.value = value, - onTap: () { - onConnect(isFileTransfer: true); - }, - child: Container( - height: 27, - alignment: Alignment.center, - decoration: BoxDecoration( - color: ftPressed.value - ? MyTheme.accent - : Colors.transparent, - border: Border.all( - color: ftPressed.value - ? MyTheme.accent - : ftHover.value - ? MyTheme.hoverBorder - : MyTheme.border, - ), - borderRadius: BorderRadius.circular(5), - ), - child: Text( - translate( - "Transfer File", - ), - style: TextStyle( - fontSize: 12, - color: ftPressed.value - ? MyTheme.color(context).bg - : MyTheme.color(context).text), - ).marginSymmetric(horizontal: 12), - ), - )), + Button( + isOutline: true, + onTap: () { + onConnect(isFileTransfer: true); + }, + text: "Transfer File", + ), const SizedBox( width: 17, ), - Obx( - () => InkWell( - onTapDown: (_) => connPressed.value = true, - onTapUp: (_) => connPressed.value = false, - onTapCancel: () => connPressed.value = false, - onHover: (value) => connHover.value = value, - onTap: onConnect, - child: ConstrainedBox( - constraints: BoxConstraints( - minWidth: 80.0, - ), - child: Container( - height: 27, - decoration: BoxDecoration( - color: connPressed.value - ? MyTheme.accent - : MyTheme.button, - border: Border.all( - color: connPressed.value - ? MyTheme.accent - : connHover.value - ? MyTheme.hoverBorder - : MyTheme.button, - ), - borderRadius: BorderRadius.circular(5), - ), - child: Center( - child: Text( - translate( - "Connect", - ), - style: TextStyle( - fontSize: 12, - color: MyTheme.color(context).bg), - ), - ).marginSymmetric(horizontal: 12), - )), - ), - ), + Button(onTap: onConnect, text: "Connect"), ], ), ) @@ -289,7 +230,11 @@ class _ConnectionPageState extends State { width: 8, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), - color: svcStopped.value ? Colors.redAccent : Colors.green, + color: svcStopped.value || svcStatusCode.value == 0 + ? kColorWarn + : (svcStatusCode.value == 1 + ? Color.fromARGB(255, 50, 190, 166) + : Color.fromARGB(255, 224, 79, 95)), ), ).paddingSymmetric(horizontal: 12.0); if (svcStopped.value) { diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index edae7deeb..1bf7feb14 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -5,6 +5,8 @@ import 'package:flutter/material.dart' hide MenuItem; import 'package:flutter/services.dart'; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/desktop/pages/connection_page.dart'; +import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart'; +import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; @@ -12,6 +14,9 @@ import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:tray_manager/tray_manager.dart'; import 'package:window_manager/window_manager.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../widgets/button.dart'; class DesktopHomePage extends StatefulWidget { const DesktopHomePage({Key? key}) : super(key: key); @@ -26,6 +31,7 @@ class _DesktopHomePageState extends State with TrayListener, WindowListener, AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; + var updateUrl = ''; @override void onWindowClose() async { @@ -68,12 +74,13 @@ class _DesktopHomePageState extends State value: gFFI.serverModel, child: Container( width: 200, - color: MyTheme.color(context).bg, + color: Theme.of(context).backgroundColor, child: Column( children: [ buildTip(context), buildIDBoard(context), buildPasswordBoard(context), + buildHelpCards(), ], ), ), @@ -82,7 +89,7 @@ class _DesktopHomePageState extends State buildRightPane(BuildContext context) { return Container( - color: MyTheme.color(context).grayBg, + color: Theme.of(context).scaffoldBackgroundColor, child: ConnectionPage(), ); } @@ -116,7 +123,11 @@ class _DesktopHomePageState extends State translate("ID"), style: TextStyle( fontSize: 14, - color: MyTheme.color(context).lightText), + color: Theme.of(context) + .textTheme + .titleLarge + ?.color + ?.withOpacity(0.5)), ).marginOnly(top: 5), buildPopupMenu(context) ], @@ -152,21 +163,20 @@ class _DesktopHomePageState extends State } Widget buildPopupMenu(BuildContext context) { + final textColor = Theme.of(context).textTheme.titleLarge?.color; RxBool hover = false.obs; return InkWell( - onTap: () async {}, + onTap: DesktopTabPage.onAddSetting, child: Obx( () => CircleAvatar( radius: 15, backgroundColor: hover.value - ? MyTheme.color(context).grayBg! - : MyTheme.color(context).bg!, + ? Theme.of(context).scaffoldBackgroundColor + : Theme.of(context).backgroundColor, child: Icon( Icons.more_vert_outlined, size: 20, - color: hover.value - ? MyTheme.color(context).text - : MyTheme.color(context).lightText, + color: hover.value ? textColor : textColor?.withOpacity(0.5), ), ), ), @@ -178,6 +188,7 @@ class _DesktopHomePageState extends State final model = gFFI.serverModel; RxBool refreshHover = false.obs; RxBool editHover = false.obs; + final textColor = Theme.of(context).textTheme.titleLarge?.color; return Container( margin: EdgeInsets.only(left: 20.0, right: 16, top: 13, bottom: 13), child: Row( @@ -198,7 +209,7 @@ class _DesktopHomePageState extends State Text( translate("Password"), style: TextStyle( - fontSize: 14, color: MyTheme.color(context).lightText), + fontSize: 14, color: textColor?.withOpacity(0.5)), ), Row( children: [ @@ -228,8 +239,8 @@ class _DesktopHomePageState extends State () => Icon( Icons.refresh, color: refreshHover.value - ? MyTheme.color(context).text - : Color(0xFFDDDDDD), + ? textColor + : Color(0xFFDDDDDD), // TODO size: 22, ).marginOnly(right: 8, bottom: 2), ), @@ -241,12 +252,12 @@ class _DesktopHomePageState extends State () => Icon( Icons.edit, color: editHover.value - ? MyTheme.color(context).text - : Color(0xFFDDDDDD), + ? textColor + : Color(0xFFDDDDDD), // TODO size: 22, ).marginOnly(right: 8, bottom: 2), ), - onTap: () => {}, + onTap: () => DesktopSettingPage.switch2page(1), onHover: (value) => editHover.value = value, ), ], @@ -270,7 +281,11 @@ class _DesktopHomePageState extends State children: [ Text( translate("Your Desktop"), - style: TextStyle(fontWeight: FontWeight.normal, fontSize: 19), + style: Theme.of(context).textTheme.titleLarge, + // style: TextStyle( + // // color: MyTheme.color(context).text, + // fontWeight: FontWeight.normal, + // fontSize: 19), ), SizedBox( height: 10.0, @@ -278,16 +293,93 @@ class _DesktopHomePageState extends State Text( translate("desk_tip"), overflow: TextOverflow.clip, - style: TextStyle( - fontSize: 12, - color: MyTheme.color(context).lighterText, - height: 1.25), + style: Theme.of(context).textTheme.bodySmall, ) ], ), ); } + Widget buildHelpCards() { + if (Platform.isWindows) { + if (!bind.mainIsInstalled()) { + return buildInstallCard( + "", "install_tip", "Install", bind.mainGotoInstall); + } else if (bind.mainIsInstalledLowerVersion()) { + return buildInstallCard("Status", "Your installation is lower version.", + "Click to upgrade", bind.mainUpdateMe); + } + } + if (updateUrl.isNotEmpty) { + return buildInstallCard( + "Status", + "There is a newer version of ${bind.mainGetAppNameSync()} ${bind.mainGetNewVersion()} available.", + "Click to download", () async { + final Uri url = Uri.parse('https://rustdesk.com'); + await launchUrl(url); + }); + } + if (Platform.isMacOS) {} + if (bind.mainIsInstalledLowerVersion()) {} + return Container(); + } + + Widget buildInstallCard(String title, String content, String btnText, + GestureTapCallback onPressed) { + return Container( + margin: EdgeInsets.only(top: 20), + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromARGB(255, 226, 66, 188), + Color.fromARGB(255, 244, 114, 124), + ], + )), + padding: EdgeInsets.all(20), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: (title.isNotEmpty + ? [ + Center( + child: Text( + translate(title), + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 15), + ).marginOnly(bottom: 6)), + ] + : []) + + [ + Text( + translate(content), + style: TextStyle( + height: 1.5, + color: Colors.white, + fontWeight: FontWeight.normal, + fontSize: 13), + ).marginOnly(bottom: 20), + Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + Button( + padding: 8, + isOutline: true, + text: translate(btnText), + textColor: Colors.white, + borderColor: Colors.white, + textSize: 20, + radius: 10, + onTap: onPressed, + ) + ]), + ], + )), + ); + } + @override void onTrayMenuItemClick(MenuItem menuItem) { debugPrint('click ${menuItem.key}'); @@ -305,6 +397,10 @@ class _DesktopHomePageState extends State @override void initState() { super.initState(); + Timer(const Duration(seconds: 5), () async { + updateUrl = await bind.mainGetSoftwareUpdateUrl(); + if (updateUrl.isNotEmpty) setState(() {}); + }); trayManager.addListener(this); windowManager.addListener(this); rustDeskWinManager.setMethodHandler((call, fromWindowId) async { @@ -331,7 +427,7 @@ Future loginDialog() async { var userNameMsg = ""; String pass = ""; var passMsg = ""; - var userContontroller = TextEditingController(text: userName); + var userController = TextEditingController(text: userName); var pwdController = TextEditingController(text: pass); var isInProgress = false; @@ -349,7 +445,7 @@ Future loginDialog() async { }); } - userName = userContontroller.text; + userName = userController.text; pass = pwdController.text; if (userName.isEmpty) { userNameMsg = translate("Username missed"); @@ -385,6 +481,7 @@ Future loginDialog() async { close(); } + // 登录dialog return CustomAlertDialog( title: Text(translate("Login")), content: ConstrainedBox( @@ -411,7 +508,7 @@ Future loginDialog() async { decoration: InputDecoration( border: const OutlineInputBorder(), errorText: userNameMsg.isNotEmpty ? userNameMsg : null), - controller: userContontroller, + controller: userController, focusNode: FocusNode()..requestFocus(), ), ), diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 119abda08..1c28fdd98 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -5,7 +5,9 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hbb/common.dart'; +import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; +import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:get/get.dart'; @@ -18,7 +20,7 @@ import '../../common/widgets/dialog.dart'; const double _kTabWidth = 235; const double _kTabHeight = 42; -const double _kCardFixedWidth = 560; +const double _kCardFixedWidth = 540; const double _kCardLeftMargin = 15; const double _kContentHMargin = 15; const double _kContentHSubMargin = _kContentHMargin + 33; @@ -28,6 +30,8 @@ const double _kListViewBottomMargin = 15; const double _kTitleFontSize = 20; const double _kContentFontSize = 15; const Color _accentColor = MyTheme.accent; +const String _kSettingPageControllerTag = "settingPageController"; +const String _kSettingPageIndexTag = "settingPageIndex"; class _TabInfo { late final String label; @@ -37,10 +41,30 @@ class _TabInfo { } class DesktopSettingPage extends StatefulWidget { - const DesktopSettingPage({Key? key}) : super(key: key); + final int initialPage; + + const DesktopSettingPage({Key? key, required this.initialPage}) + : super(key: key); @override State createState() => _DesktopSettingPageState(); + + static void switch2page(int page) { + if (page >= 5) return; + try { + if (Get.isRegistered(tag: _kSettingPageControllerTag)) { + DesktopTabPage.onAddSetting(initialPage: page); + PageController controller = Get.find(tag: _kSettingPageControllerTag); + RxInt selectedIndex = Get.find(tag: _kSettingPageIndexTag); + selectedIndex.value = page; + controller.jumpToPage(page); + } else { + DesktopTabPage.onAddSetting(initialPage: page); + } + } catch (e) { + debugPrint('$e'); + } + } } class _DesktopSettingPageState extends State @@ -50,12 +74,12 @@ class _DesktopSettingPageState extends State _TabInfo('Security', Icons.enhanced_encryption_outlined, Icons.enhanced_encryption), _TabInfo('Network', Icons.link_outlined, Icons.link), - _TabInfo('Acount', Icons.person_outline, Icons.person), + _TabInfo('Account', Icons.person_outline, Icons.person), _TabInfo('About', Icons.info_outline, Icons.info) ]; late PageController controller; - RxInt selectedIndex = 0.obs; + late RxInt selectedIndex; @override bool get wantKeepAlive => true; @@ -63,14 +87,24 @@ class _DesktopSettingPageState extends State @override void initState() { super.initState(); - controller = PageController(); + selectedIndex = (widget.initialPage < 5 ? widget.initialPage : 0).obs; + Get.put(selectedIndex, tag: _kSettingPageIndexTag); + controller = PageController(initialPage: widget.initialPage); + Get.put(controller, tag: _kSettingPageControllerTag); + } + + @override + void dispose() { + super.dispose(); + Get.delete(tag: _kSettingPageControllerTag); + Get.delete(tag: _kSettingPageIndexTag); } @override Widget build(BuildContext context) { super.build(context); return Scaffold( - backgroundColor: MyTheme.color(context).bg, + backgroundColor: Theme.of(context).backgroundColor, body: Row( children: [ SizedBox( @@ -85,7 +119,7 @@ class _DesktopSettingPageState extends State const VerticalDivider(thickness: 1, width: 1), Expanded( child: Container( - color: MyTheme.color(context).grayBg, + color: Theme.of(context).scaffoldBackgroundColor, child: DesktopScrollWrapper( scrollController: controller, child: PageView( @@ -94,7 +128,7 @@ class _DesktopSettingPageState extends State _General(), _Safety(), _Network(), - _Acount(), + _Account(), _About(), ], )), @@ -387,7 +421,7 @@ class _Safety extends StatefulWidget { class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; - bool locked = true; + bool locked = bind.mainIsInstalled(); final scrollController = ScrollController(); @override @@ -533,7 +567,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { _OptionCheckBox(context, 'Deny remote access', 'stop-service', checkedIcon: const Icon( Icons.warning_amber_rounded, - color: Color.fromARGB(255, 255, 204, 0), + color: kColorWarn, ), enabled: enabled), Offstage( @@ -541,6 +575,8 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { child: _OptionCheckBox(context, 'Enable RDP', 'enable-rdp', enabled: enabled), ), + _OptionCheckBox(context, 'Deny LAN Discovery', 'enable-lan-discovery', + reverse: true, enabled: enabled), ...directIp(context), whitelist(), ]); @@ -700,14 +736,14 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { } } -class _Acount extends StatefulWidget { - const _Acount({Key? key}) : super(key: key); +class _Account extends StatefulWidget { + const _Account({Key? key}) : super(key: key); @override - State<_Acount> createState() => _AcountState(); + State<_Account> createState() => _AccountState(); } -class _AcountState extends State<_Acount> { +class _AccountState extends State<_Account> { @override Widget build(BuildContext context) { final scrollController = ScrollController(); @@ -717,12 +753,12 @@ class _AcountState extends State<_Acount> { physics: NeverScrollableScrollPhysics(), controller: scrollController, children: [ - _Card(title: 'Acount', children: [login()]), + _Card(title: 'Account', children: [accountAction()]), ], ).marginOnly(bottom: _kListViewBottomMargin)); } - Widget login() { + Widget accountAction() { return _futureBuilder(future: () async { return await gFFI.userModel.getUserName(); }(), hasData: (data) { @@ -730,12 +766,14 @@ class _AcountState extends State<_Acount> { return _Button( username.isEmpty ? 'Login' : 'Logout', () => { - loginDialog().then((success) { - if (success) { - // refresh frame - setState(() {}); - } - }) + username.isEmpty + ? loginDialog().then((success) { + if (success) { + // refresh frame + setState(() {}); + } + }) + : gFFI.userModel.logOut() }); }); } @@ -859,7 +897,9 @@ Widget _Card({required String title, required List children}) { } Color? _disabledTextColor(BuildContext context, bool enabled) { - return enabled ? null : MyTheme.color(context).lighterText; + return enabled + ? null + : Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6); } // ignore: non_constant_identifier_names @@ -1339,91 +1379,6 @@ void changeServer() async { }); } -void changeWhiteList({Function()? callback}) async { - Map oldOptions = jsonDecode(await bind.mainGetOptions()); - var newWhiteList = ((oldOptions['whitelist'] ?? "") as String).split(','); - var newWhiteListField = newWhiteList.join('\n'); - var controller = TextEditingController(text: newWhiteListField); - var msg = ""; - var isInProgress = false; - gFFI.dialogManager.show((setState, close) { - return CustomAlertDialog( - title: Text(translate("IP Whitelisting")), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(translate("whitelist_sep")), - const SizedBox( - height: 8.0, - ), - Row( - children: [ - Expanded( - child: TextField( - maxLines: null, - decoration: InputDecoration( - border: const OutlineInputBorder(), - errorText: msg.isEmpty ? null : translate(msg), - ), - controller: controller, - focusNode: FocusNode()..requestFocus()), - ), - ], - ), - const SizedBox( - height: 4.0, - ), - Offstage( - offstage: !isInProgress, child: const LinearProgressIndicator()) - ], - ), - actions: [ - TextButton(onPressed: close, child: Text(translate("Cancel"))), - TextButton( - onPressed: () async { - await bind.mainSetOption(key: 'whitelist', value: ''); - callback?.call(); - close(); - }, - child: Text(translate("Clear"))), - TextButton( - onPressed: () async { - setState(() { - msg = ""; - isInProgress = true; - }); - newWhiteListField = controller.text.trim(); - var newWhiteList = ""; - if (newWhiteListField.isEmpty) { - // pass - } else { - final ips = - newWhiteListField.trim().split(RegExp(r"[\s,;\n]+")); - // test ip - final ipMatch = RegExp(r"^\d+\.\d+\.\d+\.\d+$"); - for (final ip in ips) { - if (!ipMatch.hasMatch(ip)) { - msg = "${translate("Invalid IP")} $ip"; - setState(() { - isInProgress = false; - }); - return; - } - } - newWhiteList = ips.join(','); - } - oldOptions['whitelist'] = newWhiteList; - await bind.mainSetOptions(json: jsonEncode(oldOptions)); - callback?.call(); - close(); - }, - child: Text(translate("OK"))), - ], - onCancel: close, - ); - }); -} - void changeSocks5Proxy() async { var socks = await bind.mainGetSocks(); diff --git a/flutter/lib/desktop/pages/desktop_tab_page.dart b/flutter/lib/desktop/pages/desktop_tab_page.dart index 2f24ddbde..82a9227c7 100644 --- a/flutter/lib/desktop/pages/desktop_tab_page.dart +++ b/flutter/lib/desktop/pages/desktop_tab_page.dart @@ -14,6 +14,23 @@ class DesktopTabPage extends StatefulWidget { @override State createState() => _DesktopTabPageState(); + + static void onAddSetting({int initialPage = 0}) { + try { + DesktopTabController tabController = Get.find(); + tabController.add(TabInfo( + key: kTabLabelSettingPage, + label: kTabLabelSettingPage, + selectedIcon: Icons.build_sharp, + unselectedIcon: Icons.build_outlined, + page: DesktopSettingPage( + key: const ValueKey(kTabLabelSettingPage), + initialPage: initialPage, + ))); + } catch (e) { + debugPrint('$e'); + } + } } class _DesktopTabPageState extends State { @@ -22,6 +39,7 @@ class _DesktopTabPageState extends State { @override void initState() { super.initState(); + Get.put(tabController); tabController.add(TabInfo( key: kTabLabelHomePage, label: kTabLabelHomePage, @@ -33,6 +51,12 @@ class _DesktopTabPageState extends State { ))); } + @override + void dispose() { + super.dispose(); + Get.delete(); + } + @override Widget build(BuildContext context) { RxBool fullscreen = false.obs; @@ -42,13 +66,13 @@ class _DesktopTabPageState extends State { OverlayEntry(builder: (context) { gFFI.dialogManager.setOverlayState(Overlay.of(context)); return Scaffold( - backgroundColor: MyTheme.color(context).bg, + backgroundColor: Theme.of(context).backgroundColor, body: DesktopTab( controller: tabController, tail: ActionIcon( message: 'Settings', icon: IconFont.menu, - onTap: onAddSetting, + onTap: DesktopTabPage.onAddSetting, isClose: false, ), )); @@ -62,13 +86,4 @@ class _DesktopTabPageState extends State { fullscreen.value ? kFullScreenEdgeSize : kWindowEdgeSize, child: tabWidget)); } - - void onAddSetting() { - tabController.add(TabInfo( - key: kTabLabelSettingPage, - label: kTabLabelSettingPage, - selectedIcon: Icons.build_sharp, - unselectedIcon: Icons.build_outlined, - page: DesktopSettingPage(key: const ValueKey(kTabLabelSettingPage)))); - } } diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index ced61e8d9..f44088d6e 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -104,7 +104,7 @@ class _FileManagerPageState extends State return false; }, child: Scaffold( - backgroundColor: MyTheme.color(context).bg, + backgroundColor: Theme.of(context).backgroundColor, body: Row( children: [ Flexible(flex: 3, child: body(isLocal: true)), diff --git a/flutter/lib/desktop/pages/file_manager_tab_page.dart b/flutter/lib/desktop/pages/file_manager_tab_page.dart index 9b8060bb7..24a36eddb 100644 --- a/flutter/lib/desktop/pages/file_manager_tab_page.dart +++ b/flutter/lib/desktop/pages/file_manager_tab_page.dart @@ -72,7 +72,7 @@ class _FileManagerTabPageState extends State { decoration: BoxDecoration( border: Border.all(color: MyTheme.color(context).border!)), child: Scaffold( - backgroundColor: MyTheme.color(context).bg, + backgroundColor: Theme.of(context).backgroundColor, body: DesktopTab( controller: tabController, onWindowCloseButton: handleWindowCloseButton, diff --git a/flutter/lib/desktop/pages/port_forward_page.dart b/flutter/lib/desktop/pages/port_forward_page.dart index 28aa8d3cf..ccbf1805e 100644 --- a/flutter/lib/desktop/pages/port_forward_page.dart +++ b/flutter/lib/desktop/pages/port_forward_page.dart @@ -70,7 +70,7 @@ class _PortForwardPageState extends State Widget build(BuildContext context) { super.build(context); return Scaffold( - backgroundColor: MyTheme.color(context).grayBg, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, body: FutureBuilder(future: () async { if (!widget.isRDP) { refreshTunnelConfig(); @@ -80,7 +80,8 @@ class _PortForwardPageState extends State return Container( decoration: BoxDecoration( border: Border.all( - width: 20, color: MyTheme.color(context).grayBg!)), + width: 20, + color: Theme.of(context).scaffoldBackgroundColor)), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -88,7 +89,7 @@ class _PortForwardPageState extends State Flexible( child: Container( decoration: BoxDecoration( - color: MyTheme.color(context).bg, + color: Theme.of(context).backgroundColor, border: Border.all(width: 1, color: MyTheme.border)), child: widget.isRDP ? buildRdp(context) : buildTunnel(context), @@ -131,7 +132,7 @@ class _PortForwardPageState extends State return Theme( data: Theme.of(context) - .copyWith(backgroundColor: MyTheme.color(context).bg), + .copyWith(backgroundColor: Theme.of(context).backgroundColor), child: Obx(() => ListView.builder( controller: ScrollController(), itemCount: pfs.length + 2, @@ -139,7 +140,7 @@ class _PortForwardPageState extends State if (index == 0) { return Container( height: 25, - color: MyTheme.color(context).grayBg, + color: Theme.of(context).scaffoldBackgroundColor, child: Row(children: [ text('Local Port'), const SizedBox(width: _kColumn1Width), @@ -166,7 +167,7 @@ class _PortForwardPageState extends State return Container( height: _kRowHeight, - decoration: BoxDecoration(color: MyTheme.color(context).bg), + decoration: BoxDecoration(color: Theme.of(context).backgroundColor), child: Row(children: [ buildTunnelInputCell(context, controller: localPortController, @@ -216,11 +217,12 @@ class _PortForwardPageState extends State {required TextEditingController controller, List? inputFormatters, String? hint}) { + final textColor = Theme.of(context).textTheme.titleLarge?.color; return Expanded( child: TextField( controller: controller, inputFormatters: inputFormatters, - cursorColor: MyTheme.color(context).text, + cursorColor: textColor, cursorHeight: 20, cursorWidth: 1, decoration: InputDecoration( @@ -228,12 +230,12 @@ class _PortForwardPageState extends State borderSide: BorderSide(color: MyTheme.color(context).border!)), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: MyTheme.color(context).border!)), - fillColor: MyTheme.color(context).bg, + fillColor: Theme.of(context).backgroundColor, contentPadding: const EdgeInsets.all(10), hintText: hint, - hintStyle: TextStyle( - color: MyTheme.color(context).placeholder, fontSize: 16)), - style: TextStyle(color: MyTheme.color(context).text, fontSize: 16), + hintStyle: + TextStyle(color: Theme.of(context).hintColor, fontSize: 16)), + style: TextStyle(color: textColor, fontSize: 16), ).marginAll(10), ); } @@ -250,7 +252,7 @@ class _PortForwardPageState extends State ? MyTheme.currentThemeMode() == ThemeMode.dark ? const Color(0xFF202020) : const Color(0xFFF4F5F6) - : MyTheme.color(context).bg), + : Theme.of(context).backgroundColor), child: Row(children: [ text(pf.localPort.toString()), const SizedBox(width: _kColumn1Width), @@ -292,7 +294,7 @@ class _PortForwardPageState extends State ).marginOnly(left: _kTextLeftMargin)); return Theme( data: Theme.of(context) - .copyWith(backgroundColor: MyTheme.color(context).bg), + .copyWith(backgroundColor: Theme.of(context).backgroundColor), child: ListView.builder( controller: ScrollController(), itemCount: 2, @@ -300,7 +302,7 @@ class _PortForwardPageState extends State if (index == 0) { return Container( height: 25, - color: MyTheme.color(context).grayBg, + color: Theme.of(context).scaffoldBackgroundColor, child: Row(children: [ text1('Local Port'), const SizedBox(width: _kColumn1Width), @@ -311,7 +313,8 @@ class _PortForwardPageState extends State } else { return Container( height: _kRowHeight, - decoration: BoxDecoration(color: MyTheme.color(context).bg), + decoration: + BoxDecoration(color: Theme.of(context).backgroundColor), child: Row(children: [ Expanded( child: Align( diff --git a/flutter/lib/desktop/pages/port_forward_tab_page.dart b/flutter/lib/desktop/pages/port_forward_tab_page.dart index d4f17aaef..6c6865718 100644 --- a/flutter/lib/desktop/pages/port_forward_tab_page.dart +++ b/flutter/lib/desktop/pages/port_forward_tab_page.dart @@ -80,7 +80,7 @@ class _PortForwardTabPageState extends State { decoration: BoxDecoration( border: Border.all(color: MyTheme.color(context).border!)), child: Scaffold( - backgroundColor: MyTheme.color(context).bg, + backgroundColor: Theme.of(context).backgroundColor, body: DesktopTab( controller: tabController, onWindowCloseButton: () async { diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 4c13f4cde..f7366b960 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -11,7 +11,6 @@ import 'package:provider/provider.dart'; import 'package:wakelock/wakelock.dart'; import 'package:flutter_custom_cursor/flutter_custom_cursor.dart'; -import '../../consts.dart'; import '../widgets/remote_menubar.dart'; import '../../common.dart'; import '../../mobile/widgets/dialog.dart'; @@ -45,7 +44,6 @@ class _RemotePageState extends State late RxBool _keyboardEnabled; final FocusNode _rawKeyFocusNode = FocusNode(); - var _isPhysicalMouse = false; var _imageFocused = false; Function(bool)? _onEnterOrLeaveImage4Menubar; @@ -139,7 +137,7 @@ class _RemotePageState extends State Widget buildBody(BuildContext context) { return Scaffold( - backgroundColor: MyTheme.color(context).bg, + backgroundColor: Theme.of(context).backgroundColor, body: Overlay( initialEntries: [ OverlayEntry(builder: (context) { @@ -445,6 +443,7 @@ class ImagePainter extends CustomPainter { } class QualityMonitor extends StatelessWidget { + static const textStyle = TextStyle(color: MyTheme.grayBg); final QualityMonitorModel qualityMonitorModel; QualityMonitor(this.qualityMonitorModel); @@ -464,23 +463,23 @@ class QualityMonitor extends StatelessWidget { children: [ Text( "Speed: ${qualityMonitorModel.data.speed ?? ''}", - style: const TextStyle(color: MyTheme.grayBg), + style: textStyle, ), Text( "FPS: ${qualityMonitorModel.data.fps ?? ''}", - style: const TextStyle(color: MyTheme.grayBg), + style: textStyle, ), Text( "Delay: ${qualityMonitorModel.data.delay ?? ''} ms", - style: const TextStyle(color: MyTheme.grayBg), + style: textStyle, ), Text( "Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb", - style: const TextStyle(color: MyTheme.grayBg), + style: textStyle, ), Text( "Codec: ${qualityMonitorModel.data.codecFormat ?? ''}", - style: const TextStyle(color: MyTheme.grayBg), + style: textStyle, ), ], ), diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart index b086a2e35..e84b574fd 100644 --- a/flutter/lib/desktop/pages/remote_tab_page.dart +++ b/flutter/lib/desktop/pages/remote_tab_page.dart @@ -91,7 +91,7 @@ class _ConnectionTabPageState extends State { decoration: BoxDecoration( border: Border.all(color: MyTheme.color(context).border!)), child: Scaffold( - backgroundColor: MyTheme.color(context).bg, + backgroundColor: Theme.of(context).backgroundColor, body: DesktopTab( controller: tabController, showTabBar: fullscreen.isFalse, diff --git a/flutter/lib/desktop/pages/server_page.dart b/flutter/lib/desktop/pages/server_page.dart index 400def7f0..b17fe70f4 100644 --- a/flutter/lib/desktop/pages/server_page.dart +++ b/flutter/lib/desktop/pages/server_page.dart @@ -69,7 +69,7 @@ class _DesktopServerPageState extends State OverlayEntry(builder: (context) { gFFI.dialogManager.setOverlayState(Overlay.of(context)); return Scaffold( - backgroundColor: MyTheme.color(context).bg, + backgroundColor: Theme.of(context).backgroundColor, body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.start, @@ -145,7 +145,7 @@ class ConnectionManagerState extends State { windowManager.startDragging(); }, child: Container( - color: MyTheme.color(context).bg, + color: Theme.of(context).backgroundColor, ), ), ), @@ -310,14 +310,15 @@ class _CmHeaderState extends State<_CmHeader> ], ), ), - Offstage( - offstage: client.isFileTransfer, - child: IconButton( - onPressed: () => checkClickTime( - client.id, () => gFFI.chatModel.toggleCMChatPage(client.id)), - icon: Icon(Icons.message_outlined), - ), - ) + Consumer( + builder: (_, model, child) => Offstage( + offstage: !client.authorized || client.isFileTransfer, + child: IconButton( + onPressed: () => checkClickTime(client.id, + () => gFFI.chatModel.toggleCMChatPage(client.id)), + icon: Icon(Icons.message_outlined), + ), + )) ], ); } diff --git a/flutter/lib/desktop/widgets/button.dart b/flutter/lib/desktop/widgets/button.dart new file mode 100644 index 000000000..b8f1bfe83 --- /dev/null +++ b/flutter/lib/desktop/widgets/button.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../../common.dart'; + +class Button extends StatefulWidget { + GestureTapCallback onTap; + String text; + double? textSize; + double? minWidth; + bool isOutline; + double? padding; + Color? textColor; + double? radius; + Color? borderColor; + + Button({ + Key? key, + this.minWidth, + this.isOutline = false, + this.textSize, + this.padding, + this.textColor, + this.radius, + this.borderColor, + required this.onTap, + required this.text, + }) : super(key: key); + + @override + State