From 4f859d3c9df5320a15bd18982f0394f9aa4d6b04 Mon Sep 17 00:00:00 2001 From: Kingtous Date: Tue, 23 Aug 2022 17:21:50 +0800 Subject: [PATCH] feat: peer card type Signed-off-by: Kingtous --- .../lib/desktop/pages/connection_page.dart | 51 ++- flutter/lib/desktop/widgets/peer_widget.dart | 43 ++- .../lib/desktop/widgets/peercard_widget.dart | 309 ++++++++++++------ 3 files changed, 285 insertions(+), 118 deletions(-) diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index c021217e0..4e2a5639f 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -5,6 +5,7 @@ import 'package:contextmenu/contextmenu.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; import 'package:flutter_hbb/desktop/widgets/peer_widget.dart'; +import 'package:flutter_hbb/desktop/widgets/peercard_widget.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; @@ -1014,7 +1015,13 @@ class _PeerTabbedPageState extends State<_PeerTabbedPage> return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _createTabBar(context), + Row( + children: [ + Expanded(child: _createTabBar(context)), + _createSearchBar(context), + _createPeerViewTypeSwitch(context), + ], + ), _createTabBarView(), ], ); @@ -1054,4 +1061,46 @@ class _PeerTabbedPageState extends State<_PeerTabbedPage> controller: _tabController, children: super.widget.children) .paddingSymmetric(horizontal: 12.0, vertical: 4.0)); } + + _createSearchBar(BuildContext context) { + return Offstage(); + } + + _createPeerViewTypeSwitch(BuildContext context) { + final activeDeco = BoxDecoration(color: Colors.white); + return Row( + children: [ + Obx( + () => Container( + padding: EdgeInsets.all(4.0), + decoration: + peerCardUiType.value == PeerUiType.grid ? activeDeco : null, + child: InkWell( + onTap: () { + peerCardUiType.value = PeerUiType.grid; + }, + child: Icon( + Icons.grid_view_rounded, + size: 20, + )), + ), + ), + Obx( + () => Container( + padding: EdgeInsets.all(4.0), + decoration: + peerCardUiType.value == PeerUiType.list ? activeDeco : null, + child: InkWell( + onTap: () { + peerCardUiType.value = PeerUiType.list; + }, + child: Icon( + Icons.list, + size: 20, + )), + ), + ), + ], + ); + } } diff --git a/flutter/lib/desktop/widgets/peer_widget.dart b/flutter/lib/desktop/widgets/peer_widget.dart index 1a66f3a06..9014cb608 100644 --- a/flutter/lib/desktop/widgets/peer_widget.dart +++ b/flutter/lib/desktop/widgets/peer_widget.dart @@ -1,14 +1,15 @@ import 'dart:async'; -import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:visibility_detector/visibility_detector.dart'; import 'package:window_manager/window_manager.dart'; +import '../../common.dart'; import '../../models/peer_model.dart'; import '../../models/platform_model.dart'; -import '../../common.dart'; import 'peercard_widget.dart'; typedef OffstageFunc = bool Function(Peer peer); @@ -82,21 +83,25 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener { peers.peers.forEach((peer) { cards.add(Offstage( offstage: super.widget._offstageFunc(peer), - child: Container( - width: 225, - height: 150, - child: VisibilityDetector( - key: Key('${peer.id}'), - onVisibilityChanged: (info) { - final peerId = (info.key as ValueKey).value; - if (info.visibleFraction > 0.00001) { - _curPeers.add(peerId); - } else { - _curPeers.remove(peerId); - } - _lastChangeTime = DateTime.now(); - }, - child: super.widget._peerCardWidgetFunc(peer), + child: Obx( + () => Container( + width: 225, + height: peerCardUiType.value == PeerUiType.grid + ? 150 + : 50, + child: VisibilityDetector( + key: Key('${peer.id}'), + onVisibilityChanged: (info) { + final peerId = (info.key as ValueKey).value; + if (info.visibleFraction > 0.00001) { + _curPeers.add(peerId); + } else { + _curPeers.remove(peerId); + } + _lastChangeTime = DateTime.now(); + }, + child: super.widget._peerCardWidgetFunc(peer), + ), ), ))); }); @@ -162,7 +167,9 @@ class RecentPeerWidget extends BasePeerWidget { super._name = "recent peer"; super._loadEvent = "load_recent_peers"; super._offstageFunc = (Peer _peer) => false; - super._peerCardWidgetFunc = (Peer peer) => RecentPeerCard(peer: peer); + super._peerCardWidgetFunc = (Peer peer) => RecentPeerCard( + peer: peer, + ); super._initPeers = []; } diff --git a/flutter/lib/desktop/widgets/peercard_widget.dart b/flutter/lib/desktop/widgets/peercard_widget.dart index e260ef391..3ec149d60 100644 --- a/flutter/lib/desktop/widgets/peercard_widget.dart +++ b/flutter/lib/desktop/widgets/peercard_widget.dart @@ -5,13 +5,17 @@ import 'package:get/get.dart'; import '../../common.dart'; import '../../models/model.dart'; -import '../../models/platform_model.dart'; import '../../models/peer_model.dart'; +import '../../models/platform_model.dart'; typedef PopupMenuItemsFunc = Future>> Function(); enum PeerType { recent, fav, discovered, ab } +enum PeerUiType { grid, list } + +final peerCardUiType = PeerUiType.grid.obs; + class _PeerCard extends StatefulWidget { final Peer peer; final PopupMenuItemsFunc popupMenuItemsFunc; @@ -39,130 +43,237 @@ class _PeerCardState extends State<_PeerCard> final peer = super.widget.peer; var deco = Rx(BoxDecoration( border: Border.all(color: Colors.transparent, width: 1.0), - borderRadius: BorderRadius.circular(20))); - return Card( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), - child: MouseRegion( - onEnter: (evt) { - deco.value = BoxDecoration( - border: Border.all(color: Colors.blue, width: 1.0), - borderRadius: BorderRadius.circular(20)); - }, - onExit: (evt) { - deco.value = BoxDecoration( - border: Border.all(color: Colors.transparent, width: 1.0), - borderRadius: BorderRadius.circular(20)); - }, - child: GestureDetector( - onDoubleTap: () => _connect(peer.id), - child: _buildPeerTile(context, peer, deco)), - )); + borderRadius: peerCardUiType.value == PeerUiType.grid + ? BorderRadius.circular(20) + : null)); + return MouseRegion( + onEnter: (evt) { + deco.value = BoxDecoration( + border: Border.all(color: Colors.blue, width: 1.0), + borderRadius: peerCardUiType.value == PeerUiType.grid + ? BorderRadius.circular(20) + : null); + }, + onExit: (evt) { + deco.value = BoxDecoration( + border: Border.all(color: Colors.transparent, width: 1.0), + borderRadius: peerCardUiType.value == PeerUiType.grid + ? BorderRadius.circular(20) + : null); + }, + child: GestureDetector( + onDoubleTap: () => _connect(peer.id), + child: Obx(() => peerCardUiType.value == PeerUiType.grid + ? _buildPeerCard(context, peer, deco) + : _buildPeerTile(context, peer, deco))), + ); } Widget _buildPeerTile( BuildContext context, Peer peer, Rx deco) { + final greyStyle = TextStyle(fontSize: 12, color: Colors.grey); return Obx( () => Container( decoration: deco.value, - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, + child: Row( + mainAxisSize: MainAxisSize.max, children: [ + Container( + height: 50, + width: 50, + decoration: BoxDecoration( + color: str2color('${peer.id}${peer.platform}', 0x7f), + ), + alignment: Alignment.center, + child: _getPlatformImage('${peer.platform}').paddingAll(8.0), + ), Expanded( child: Container( - decoration: BoxDecoration( - color: str2color('${peer.id}${peer.platform}', 0x7f), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - ), + decoration: BoxDecoration(color: Colors.white), child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( - crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - Container( - padding: const EdgeInsets.all(6), - child: _getPlatformImage('${peer.platform}'), - ), - Row( - children: [ - Expanded( - 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, - child: Text( - name, - style: TextStyle( - color: Colors.white70, - fontSize: 12), - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - ), - ); - } else { - // alias has not arrived - return Center( - child: Text( - '${peer.username}@${peer.hostname}', - style: TextStyle( - color: Colors.white70, - fontSize: 12), - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - )); - } - }, - ), - ), - ], + Row(children: [ + Text( + '${peer.id}', + style: TextStyle(fontWeight: FontWeight.w400), + ), + Padding( + padding: EdgeInsets.fromLTRB(4, 4, 8, 4), + child: CircleAvatar( + radius: 5, + backgroundColor: peer.online + ? Colors.green + : Colors.yellow)), + ]), + 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, + 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, + ); + } + }, + ), ), ], - ).paddingAll(4.0), + ), ), + InkWell( + child: Icon(Icons.more_vert), + onTapDown: (e) { + final x = e.globalPosition.dx; + final y = e.globalPosition.dy; + _menuPos = RelativeRect.fromLTRB(x, y, x, y); + }, + onTap: () { + _showPeerMenu(context, peer.id); + }), ], - ), + ).paddingSymmetric(horizontal: 8.0), ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row(children: [ - Padding( - padding: EdgeInsets.fromLTRB(0, 4, 8, 4), - child: CircleAvatar( - radius: 5, - backgroundColor: - peer.online ? Colors.green : Colors.yellow)), - Text('${peer.id}') - ]), - InkWell( - child: Icon(Icons.more_vert), - onTapDown: (e) { - final x = e.globalPosition.dx; - final y = e.globalPosition.dy; - _menuPos = RelativeRect.fromLTRB(x, y, x, y); - }, - onTap: () { - _showPeerMenu(context, peer.id); - }), - ], - ).paddingSymmetric(vertical: 8.0, horizontal: 12.0) + ) ], ), ), ); } + Widget _buildPeerCard( + BuildContext context, Peer peer, Rx deco) { + return Card( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + child: GestureDetector( + onDoubleTap: () => _connect(peer.id), + child: Obx( + () => Container( + decoration: deco.value, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + color: str2color('${peer.id}${peer.platform}', 0x7f), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(6), + child: _getPlatformImage('${peer.platform}'), + ), + Row( + children: [ + Expanded( + 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, + child: Text( + name, + style: TextStyle( + color: Colors.white70, + fontSize: 12), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + ), + ); + } else { + // alias has not arrived + return Center( + child: Text( + '${peer.username}@${peer.hostname}', + style: TextStyle( + color: Colors.white70, + fontSize: 12), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + )); + } + }, + ), + ), + ], + ), + ], + ).paddingAll(4.0), + ), + ], + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row(children: [ + Padding( + padding: EdgeInsets.fromLTRB(0, 4, 8, 4), + child: CircleAvatar( + radius: 5, + backgroundColor: peer.online + ? Colors.green + : Colors.yellow)), + Text('${peer.id}') + ]), + InkWell( + child: Icon(Icons.more_vert), + onTapDown: (e) { + final x = e.globalPosition.dx; + final y = e.globalPosition.dy; + _menuPos = RelativeRect.fromLTRB(x, y, x, y); + }, + onTap: () { + _showPeerMenu(context, peer.id); + }), + ], + ).paddingSymmetric(vertical: 8.0, horizontal: 12.0) + ], + ), + ), + )), + ); + } + /// Connect to a peer with [id]. /// If [isFileTransfer], starts a session only for file transfer. void _connect(String id, {bool isFileTransfer = false}) async {