diff --git a/flutter/lib/common/widgets/autocomplete.dart b/flutter/lib/common/widgets/autocomplete.dart index 55e4dbcc1..966cb099f 100644 --- a/flutter/lib/common/widgets/autocomplete.dart +++ b/flutter/lib/common/widgets/autocomplete.dart @@ -6,55 +6,55 @@ import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common/widgets/peer_card.dart'; +Future> getAllPeers() async { + Map recentPeers = + jsonDecode(await bind.mainLoadRecentPeersSync()); + Map lanPeers = jsonDecode(await bind.mainLoadLanPeersSync()); + Map abPeers = jsonDecode(await bind.mainLoadAbSync()); + Map groupPeers = jsonDecode(await bind.mainLoadGroupSync()); - Future> getAllPeers() async { - Map recentPeers = jsonDecode(await bind.mainLoadRecentPeersSync()); - Map lanPeers = jsonDecode(await bind.mainLoadLanPeersSync()); - Map abPeers = jsonDecode(await bind.mainLoadAbSync()); - Map groupPeers = jsonDecode(await bind.mainLoadGroupSync()); + Map combinedPeers = {}; - Map combinedPeers = {}; + void _mergePeers(Map peers) { + if (peers.containsKey("peers")) { + dynamic peerData = peers["peers"]; - void _mergePeers(Map peers) { - if (peers.containsKey("peers")) { - dynamic peerData = peers["peers"]; - - if (peerData is String) { - try { - peerData = jsonDecode(peerData); - } catch (e) { - print("Error decoding peers: $e"); - return; - } + if (peerData is String) { + try { + peerData = jsonDecode(peerData); + } catch (e) { + print("Error decoding peers: $e"); + return; } + } - if (peerData is List) { - for (var peer in peerData) { - if (peer is Map && peer.containsKey("id")) { - String id = peer["id"]; - if (!combinedPeers.containsKey(id)) { - combinedPeers[id] = peer; - } + if (peerData is List) { + for (var peer in peerData) { + if (peer is Map && peer.containsKey("id")) { + String id = peer["id"]; + if (!combinedPeers.containsKey(id)) { + combinedPeers[id] = peer; } } } } } - - _mergePeers(recentPeers); - _mergePeers(lanPeers); - _mergePeers(abPeers); - _mergePeers(groupPeers); - - List parsedPeers = []; - - for (var peer in combinedPeers.values) { - parsedPeers.add(Peer.fromJson(peer)); - } - return parsedPeers; } - class AutocompletePeerTile extends StatefulWidget { + _mergePeers(recentPeers); + _mergePeers(lanPeers); + _mergePeers(abPeers); + _mergePeers(groupPeers); + + List parsedPeers = []; + + for (var peer in combinedPeers.values) { + parsedPeers.add(Peer.fromJson(peer)); + } + return parsedPeers; +} + +class AutocompletePeerTile extends StatefulWidget { final VoidCallback onSelect; final Peer peer; @@ -68,7 +68,7 @@ import 'package:flutter_hbb/common/widgets/peer_card.dart'; _AutocompletePeerTileState createState() => _AutocompletePeerTileState(); } -class _AutocompletePeerTileState extends State{ +class _AutocompletePeerTileState extends State { List _frontN(List list, int n) { if (list.length <= n) { return list; @@ -76,99 +76,114 @@ class _AutocompletePeerTileState extends State{ return list.sublist(0, n); } } + @override - Widget build(BuildContext context){ + Widget build(BuildContext context) { final double _tileRadius = 5; - final name = - '${widget.peer.username}${widget.peer.username.isNotEmpty && widget.peer.hostname.isNotEmpty ? '@' : ''}${widget.peer.hostname}'; - final greyStyle = TextStyle( - fontSize: 11, - color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6)); - final child = GestureDetector( - onTap: () => widget.onSelect(), - child: - Container( - height: 42, - margin: EdgeInsets.only(bottom: 5), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Container( - decoration: BoxDecoration( - color: str2color('${widget.peer.id}${widget.peer.platform}', 0x7f), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(_tileRadius), - bottomLeft: Radius.circular(_tileRadius), - ), - ), - alignment: Alignment.center, - width: 42, - height: null, - child: Padding( - padding: EdgeInsets.all(6), - child: getPlatformImage(widget.peer.platform, size: 30) - ) - ), - Expanded( - child: Container( - padding: EdgeInsets.only(left: 10), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.only( - topRight: Radius.circular(_tileRadius), - bottomRight: Radius.circular(_tileRadius), - ), - ), - child: Row( - children: [ - Expanded( - child: Container( - margin: EdgeInsets.only(top: 2), - child: Container( - margin: EdgeInsets.only(top: 2), - child: Column( - children: [ - Container( - margin: EdgeInsets.only(top: 2), - child: Row(children: [ - getOnline(8, widget.peer.online), - Expanded( - child: Text( - widget.peer.alias.isEmpty ? formatID(widget.peer.id) : widget.peer.alias, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.titleSmall, - )), - !widget.peer.alias.isEmpty? - Padding( - padding: const EdgeInsets.only(left: 5, right: 5), - child: Text( - "(${widget.peer.id})", - style: greyStyle, - overflow: TextOverflow.ellipsis, - ) - ) - : Container(), - ])), - Align( - alignment: Alignment.centerLeft, - child: Text( - name, - style: greyStyle, - textAlign: TextAlign.start, - overflow: TextOverflow.ellipsis, + final name = + '${widget.peer.username}${widget.peer.username.isNotEmpty && widget.peer.hostname.isNotEmpty ? '@' : ''}${widget.peer.hostname}'; + final greyStyle = TextStyle( + fontSize: 11, + color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6)); + final child = GestureDetector( + onTap: () => widget.onSelect(), + child: Padding( + padding: EdgeInsets.only(left: 5, right: 5), + child: Container( + height: 42, + margin: EdgeInsets.only(bottom: 5), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + decoration: BoxDecoration( + color: str2color( + '${widget.peer.id}${widget.peer.platform}', 0x7f), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(_tileRadius), + bottomLeft: Radius.circular(_tileRadius), + ), ), - ), - ], - ) - ))), - ], - ) - ), - ) - ], - ))); - final colors = - _frontN(widget.peer.tags, 25).map((e) => gFFI.abModel.getTagColor(e)).toList(); + alignment: Alignment.center, + width: 42, + height: null, + child: Padding( + padding: EdgeInsets.all(6), + child: getPlatformImage(widget.peer.platform, + size: 30))), + Expanded( + child: Container( + padding: EdgeInsets.only(left: 10), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.only( + topRight: Radius.circular(_tileRadius), + bottomRight: Radius.circular(_tileRadius), + ), + ), + child: Row( + children: [ + Expanded( + child: Container( + margin: EdgeInsets.only(top: 2), + child: Container( + margin: EdgeInsets.only(top: 2), + child: Column( + children: [ + Container( + margin: + EdgeInsets.only(top: 2), + child: Row(children: [ + getOnline( + 8, widget.peer.online), + Expanded( + child: Text( + widget.peer.alias.isEmpty + ? formatID( + widget.peer.id) + : widget.peer.alias, + overflow: + TextOverflow.ellipsis, + style: Theme.of(context) + .textTheme + .titleSmall, + )), + !widget.peer.alias.isEmpty + ? Padding( + padding: + const EdgeInsets + .only( + left: 5, + right: 5), + child: Text( + "(${widget.peer.id})", + style: greyStyle, + overflow: + TextOverflow + .ellipsis, + )) + : Container(), + ])), + Align( + alignment: Alignment.centerLeft, + child: Text( + name, + style: greyStyle, + textAlign: TextAlign.start, + overflow: + TextOverflow.ellipsis, + ), + ), + ], + )))), + ], + )), + ) + ], + )))); + final colors = _frontN(widget.peer.tags, 25) + .map((e) => gFFI.abModel.getTagColor(e)) + .toList(); return Tooltip( message: isMobile ? '' @@ -188,4 +203,4 @@ class _AutocompletePeerTileState extends State{ ]), ); } - } \ No newline at end of file +} diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index 6ad252384..4793feecf 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -308,40 +308,59 @@ class _ConnectionPageState extends State AutocompleteOnSelected onSelected, Iterable options) { double maxHeight = options.length * 50; - maxHeight = maxHeight > 200 ? 200 : maxHeight; + if (options.length == 1) { + maxHeight = 52; + } else if (options.length == 3) { + maxHeight = 146; + } else if (options.length == 4) { + maxHeight = 193; + } + maxHeight = maxHeight.clamp(0, 200); return Align( alignment: Alignment.topLeft, - child: ClipRRect( - borderRadius: BorderRadius.circular(5), - child: Material( - elevation: 4, - child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: maxHeight, - maxWidth: 319, + child: Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 5, + spreadRadius: 1, ), - child: peers.isEmpty && isPeersLoading - ? Container( - height: 80, - child: Center( - child: CircularProgressIndicator( - strokeWidth: 2, + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(5), + child: Material( + elevation: 4, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: maxHeight, + maxWidth: 319, + ), + child: peers.isEmpty && isPeersLoading + ? Container( + height: 80, + child: Center( + child: CircularProgressIndicator( + strokeWidth: 2, + ), + )) + : Padding( + padding: + const EdgeInsets.only(top: 5), + child: ListView( + children: options + .map((peer) => + AutocompletePeerTile( + onSelect: () => + onSelected(peer), + peer: peer)) + .toList(), + ), ), - )) - : Padding( - padding: const EdgeInsets.only(top: 5), - child: ListView( - children: options - .map((peer) => AutocompletePeerTile( - onSelect: () => - onSelected(peer), - peer: peer)) - .toList(), - ), - ), - ), - )), + ), + ))), ); }, )), diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index 824ceafec..dfd7132fb 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -52,6 +52,7 @@ class _ConnectionPageState extends State { return list.sublist(0, n); } } + bool isPeersLoading = false; bool isPeersLoaded = false; StreamSubscription? _uniLinksSubscription; @@ -137,9 +138,9 @@ class _ConnectionPageState extends State { await Future.delayed(Duration(milliseconds: 100)); peers = await getAllPeers(); setState(() { - isPeersLoading = false; - isPeersLoaded = true; - }); + isPeersLoading = false; + isPeersLoaded = true; + }); } /// UI for the remote ID TextField. @@ -163,9 +164,8 @@ class _ConnectionPageState extends State { optionsBuilder: (TextEditingValue textEditingValue) { if (textEditingValue.text == '') { return const Iterable.empty(); - } - else if (peers.isEmpty && !isPeersLoaded) { - Peer emptyPeer = Peer( + } else if (peers.isEmpty && !isPeersLoaded) { + Peer emptyPeer = Peer( id: '', username: '', hostname: '', @@ -179,9 +179,9 @@ class _ConnectionPageState extends State { loginName: '', ); return [emptyPeer]; - } - else { - String textWithoutSpaces = textEditingValue.text.replaceAll(" ", ""); + } else { + String textWithoutSpaces = + textEditingValue.text.replaceAll(" ", ""); if (int.tryParse(textWithoutSpaces) != null) { textEditingValue = TextEditingValue( text: textWithoutSpaces, @@ -190,62 +190,71 @@ class _ConnectionPageState extends State { } String textToFind = textEditingValue.text.toLowerCase(); - return peers.where((peer) => - peer.id.toLowerCase().contains(textToFind) || - peer.username.toLowerCase().contains(textToFind) || - peer.hostname.toLowerCase().contains(textToFind) || - peer.alias.toLowerCase().contains(textToFind)) - .toList(); + return peers + .where((peer) => + peer.id.toLowerCase().contains(textToFind) || + peer.username + .toLowerCase() + .contains(textToFind) || + peer.hostname + .toLowerCase() + .contains(textToFind) || + peer.alias.toLowerCase().contains(textToFind)) + .toList(); } }, fieldViewBuilder: (BuildContext context, - TextEditingController fieldTextEditingController, - FocusNode fieldFocusNode, VoidCallback onFieldSubmitted) { + TextEditingController fieldTextEditingController, + FocusNode fieldFocusNode, + VoidCallback onFieldSubmitted) { fieldTextEditingController.text = _idController.text; - fieldFocusNode.addListener(() async{ - _idEmpty.value = fieldTextEditingController.text.isEmpty; - if (fieldFocusNode.hasFocus && !isPeersLoading){ + fieldFocusNode.addListener(() async { + _idEmpty.value = + fieldTextEditingController.text.isEmpty; + if (fieldFocusNode.hasFocus && !isPeersLoading) { _fetchPeers(); } }); - final textLength = fieldTextEditingController.value.text.length; + final textLength = + fieldTextEditingController.value.text.length; // select all to facilitate removing text, just following the behavior of address input of chrome - fieldTextEditingController.selection = TextSelection(baseOffset: 0, extentOffset: textLength); - return AutoSizeTextField( - controller: fieldTextEditingController, - focusNode: fieldFocusNode, - minFontSize: 18, - autocorrect: false, - enableSuggestions: false, - keyboardType: TextInputType.visiblePassword, - // keyboardType: TextInputType.number, - onChanged: (String text) { - _idController.id = text; - }, - style: const TextStyle( - fontFamily: 'WorkSans', - fontWeight: FontWeight.bold, - fontSize: 30, - color: MyTheme.idColor, - ), - decoration: InputDecoration( - labelText: translate('Remote ID'), - // hintText: 'Enter your remote ID', - border: InputBorder.none, - helperStyle: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - color: MyTheme.darkGray, - ), - labelStyle: const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - letterSpacing: 0.2, - color: MyTheme.darkGray, - ), - ), - inputFormatters: [IDTextInputFormatter()], - ); + fieldTextEditingController.selection = TextSelection( + baseOffset: 0, extentOffset: textLength); + return AutoSizeTextField( + controller: fieldTextEditingController, + focusNode: fieldFocusNode, + minFontSize: 18, + autocorrect: false, + enableSuggestions: false, + keyboardType: TextInputType.visiblePassword, + // keyboardType: TextInputType.number, + onChanged: (String text) { + _idController.id = text; + }, + style: const TextStyle( + fontFamily: 'WorkSans', + fontWeight: FontWeight.bold, + fontSize: 30, + color: MyTheme.idColor, + ), + decoration: InputDecoration( + labelText: translate('Remote ID'), + // hintText: 'Enter your remote ID', + border: InputBorder.none, + helperStyle: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: MyTheme.darkGray, + ), + labelStyle: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + letterSpacing: 0.2, + color: MyTheme.darkGray, + ), + ), + inputFormatters: [IDTextInputFormatter()], + ); }, onSelected: (option) { setState(() { @@ -253,32 +262,59 @@ class _ConnectionPageState extends State { FocusScope.of(context).unfocus(); }); }, - optionsViewBuilder: (BuildContext context, AutocompleteOnSelected onSelected, Iterable options) { + optionsViewBuilder: (BuildContext context, + AutocompleteOnSelected onSelected, + Iterable options) { double maxHeight = options.length * 50; - maxHeight = maxHeight > 200 ? 200 : maxHeight; + if (options.length == 1) { + maxHeight = 52; + } else if (options.length == 3) { + maxHeight = 146; + } else if (options.length == 4) { + maxHeight = 193; + } + maxHeight = maxHeight.clamp(0, 200); return Align( - alignment: Alignment.topLeft, - child: ClipRRect( - borderRadius: BorderRadius.circular(5), - child: Material( - elevation: 4, - child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: maxHeight, - maxWidth: 320, - ), - child: peers.isEmpty && isPeersLoading - ? Container( - height: 80, - child: Center( - child: CircularProgressIndicator( - strokeWidth: 2, - ))) - : ListView( - padding: EdgeInsets.only(top: 5), - children: options.map((peer) => AutocompletePeerTile(onSelect: () => onSelected(peer), peer: peer)).toList(), - )))) - ); + alignment: Alignment.topLeft, + child: Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 5, + spreadRadius: 1, + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(5), + child: Material( + elevation: 4, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: maxHeight, + maxWidth: 320, + ), + child: peers.isEmpty && isPeersLoading + ? Container( + height: 80, + child: Center( + child: + CircularProgressIndicator( + strokeWidth: 2, + ))) + : ListView( + padding: + EdgeInsets.only(top: 5), + children: options + .map((peer) => + AutocompletePeerTile( + onSelect: () => + onSelected( + peer), + peer: peer)) + .toList(), + )))))); }, ), ),