Merge pull request #1336 from Kingtous/flutter_desktop

feat: find id and grid/tile view for peers
This commit is contained in:
RustDesk 2022-08-23 17:57:38 +08:00 committed by GitHub
commit 75b04b9af1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 342 additions and 131 deletions

View File

@ -5,6 +5,7 @@ import 'package:contextmenu/contextmenu.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/desktop/pages/desktop_home_page.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/peer_widget.dart';
import 'package:flutter_hbb/desktop/widgets/peercard_widget.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -976,6 +977,9 @@ class _PeerTabbedPageState extends State<_PeerTabbedPage>
// hard code for now // hard code for now
void _handleTabSelection() { void _handleTabSelection() {
if (_tabController.indexIsChanging) { if (_tabController.indexIsChanging) {
// reset search text
peerSearchText.value = "";
peerSearchTextController.clear();
_tabIndex.value = _tabController.index; _tabIndex.value = _tabController.index;
switch (_tabController.index) { switch (_tabController.index) {
case 0: case 0:
@ -1014,7 +1018,13 @@ class _PeerTabbedPageState extends State<_PeerTabbedPage>
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_createTabBar(context), Row(
children: [
Expanded(child: _createTabBar(context)),
_createSearchBar(context),
_createPeerViewTypeSwitch(context),
],
),
_createTabBarView(), _createTabBarView(),
], ],
); );
@ -1054,4 +1064,72 @@ class _PeerTabbedPageState extends State<_PeerTabbedPage>
controller: _tabController, children: super.widget.children) controller: _tabController, children: super.widget.children)
.paddingSymmetric(horizontal: 12.0, vertical: 4.0)); .paddingSymmetric(horizontal: 12.0, vertical: 4.0));
} }
_createSearchBar(BuildContext context) {
return Container(
width: 175,
height: 30,
margin: EdgeInsets.only(right: 16),
decoration: BoxDecoration(color: Colors.white),
child: Obx(
() => TextField(
controller: peerSearchTextController,
onChanged: (searchText) {
peerSearchText.value = searchText;
},
decoration: InputDecoration(
prefixIcon: Icon(
Icons.search,
size: 20,
),
contentPadding: EdgeInsets.zero,
hintText: translate("Search ID"),
hintStyle: TextStyle(fontSize: 14),
border: OutlineInputBorder(),
isDense: true,
),
),
),
);
}
_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,
color: Colors.black54,
)),
),
),
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: 24,
color: Colors.black54,
)),
),
),
],
).paddingOnly(right: 16.0);
}
} }

View File

@ -1,23 +1,30 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:visibility_detector/visibility_detector.dart'; import 'package:visibility_detector/visibility_detector.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import '../../common.dart';
import '../../models/peer_model.dart'; import '../../models/peer_model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
import '../../common.dart';
import 'peercard_widget.dart'; import 'peercard_widget.dart';
typedef OffstageFunc = bool Function(Peer peer); typedef OffstageFunc = bool Function(Peer peer);
typedef PeerCardWidgetFunc = Widget Function(Peer peer); typedef PeerCardWidgetFunc = Widget Function(Peer peer);
/// for peer search text, global obs value
final peerSearchText = "".obs;
final peerSearchTextController =
TextEditingController(text: peerSearchText.value);
class _PeerWidget extends StatefulWidget { class _PeerWidget extends StatefulWidget {
late final _peers; late final _peers;
late final OffstageFunc _offstageFunc; late final OffstageFunc _offstageFunc;
late final PeerCardWidgetFunc _peerCardWidgetFunc; late final PeerCardWidgetFunc _peerCardWidgetFunc;
_PeerWidget(Peers peers, OffstageFunc offstageFunc, _PeerWidget(Peers peers, OffstageFunc offstageFunc,
PeerCardWidgetFunc peerCardWidgetFunc, PeerCardWidgetFunc peerCardWidgetFunc,
{Key? key}) {Key? key})
@ -71,20 +78,32 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final space = 8.0; final space = 12.0;
return ChangeNotifierProvider<Peers>( return ChangeNotifierProvider<Peers>(
create: (context) => super.widget._peers, create: (context) => super.widget._peers,
child: SingleChildScrollView(
child: Consumer<Peers>( child: Consumer<Peers>(
builder: (context, peers, child) => Wrap( builder: (context, peers, child) => peers.peers.isEmpty
children: () { ? Center(
child: Text(translate("Empty")),
)
: SingleChildScrollView(
child: ObxValue<RxString>((searchText) {
final cards = <Widget>[]; final cards = <Widget>[];
peers.peers.forEach((peer) { peers.peers.where((peer) {
if (searchText.isEmpty) {
return true;
} else {
return peer.id.contains(peerSearchText.value);
}
}).forEach((peer) {
cards.add(Offstage( cards.add(Offstage(
offstage: super.widget._offstageFunc(peer), offstage: super.widget._offstageFunc(peer),
child: Container( child: Obx(
() => Container(
width: 225, width: 225,
height: 150, height: peerCardUiType.value == PeerUiType.grid
? 150
: 50,
child: VisibilityDetector( child: VisibilityDetector(
key: Key('${peer.id}'), key: Key('${peer.id}'),
onVisibilityChanged: (info) { onVisibilityChanged: (info) {
@ -98,12 +117,13 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
}, },
child: super.widget._peerCardWidgetFunc(peer), child: super.widget._peerCardWidgetFunc(peer),
), ),
),
))); )));
}); });
return cards; return Wrap(
}(), children: cards, spacing: space, runSpacing: space);
spacing: space, }, peerSearchText),
runSpacing: space))), )),
); );
} }
@ -162,7 +182,9 @@ class RecentPeerWidget extends BasePeerWidget {
super._name = "recent peer"; super._name = "recent peer";
super._loadEvent = "load_recent_peers"; super._loadEvent = "load_recent_peers";
super._offstageFunc = (Peer _peer) => false; super._offstageFunc = (Peer _peer) => false;
super._peerCardWidgetFunc = (Peer peer) => RecentPeerCard(peer: peer); super._peerCardWidgetFunc = (Peer peer) => RecentPeerCard(
peer: peer,
);
super._initPeers = []; super._initPeers = [];
} }

View File

@ -5,13 +5,17 @@ import 'package:get/get.dart';
import '../../common.dart'; import '../../common.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart';
import '../../models/peer_model.dart'; import '../../models/peer_model.dart';
import '../../models/platform_model.dart';
typedef PopupMenuItemsFunc = Future<List<PopupMenuItem<String>>> Function(); typedef PopupMenuItemsFunc = Future<List<PopupMenuItem<String>>> Function();
enum PeerType { recent, fav, discovered, ab } enum PeerType { recent, fav, discovered, ab }
enum PeerUiType { grid, list }
final peerCardUiType = PeerUiType.grid.obs;
class _PeerCard extends StatefulWidget { class _PeerCard extends StatefulWidget {
final Peer peer; final Peer peer;
final PopupMenuItemsFunc popupMenuItemsFunc; final PopupMenuItemsFunc popupMenuItemsFunc;
@ -39,29 +43,134 @@ class _PeerCardState extends State<_PeerCard>
final peer = super.widget.peer; final peer = super.widget.peer;
var deco = Rx<BoxDecoration?>(BoxDecoration( var deco = Rx<BoxDecoration?>(BoxDecoration(
border: Border.all(color: Colors.transparent, width: 1.0), border: Border.all(color: Colors.transparent, width: 1.0),
borderRadius: BorderRadius.circular(20))); borderRadius: peerCardUiType.value == PeerUiType.grid
return Card( ? BorderRadius.circular(20)
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), : null));
child: MouseRegion( return MouseRegion(
onEnter: (evt) { onEnter: (evt) {
deco.value = BoxDecoration( deco.value = BoxDecoration(
border: Border.all(color: Colors.blue, width: 1.0), border: Border.all(color: Colors.blue, width: 1.0),
borderRadius: BorderRadius.circular(20)); borderRadius: peerCardUiType.value == PeerUiType.grid
? BorderRadius.circular(20)
: null);
}, },
onExit: (evt) { onExit: (evt) {
deco.value = BoxDecoration( deco.value = BoxDecoration(
border: Border.all(color: Colors.transparent, width: 1.0), border: Border.all(color: Colors.transparent, width: 1.0),
borderRadius: BorderRadius.circular(20)); borderRadius: peerCardUiType.value == PeerUiType.grid
? BorderRadius.circular(20)
: null);
}, },
child: GestureDetector( child: GestureDetector(
onDoubleTap: () => _connect(peer.id), onDoubleTap: () => _connect(peer.id),
child: _buildPeerTile(context, peer, deco)), child: Obx(() => peerCardUiType.value == PeerUiType.grid
)); ? _buildPeerCard(context, peer, deco)
: _buildPeerTile(context, peer, deco))),
);
} }
Widget _buildPeerTile( Widget _buildPeerTile(
BuildContext context, Peer peer, Rx<BoxDecoration?> deco) { BuildContext context, Peer peer, Rx<BoxDecoration?> deco) {
final greyStyle = TextStyle(fontSize: 12, color: Colors.grey);
return Obx( return Obx(
() => Container(
decoration: deco.value,
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: Colors.white),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
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<String>(
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,
);
}
},
),
),
],
),
),
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),
),
)
],
),
),
);
}
Widget _buildPeerCard(
BuildContext context, Peer peer, Rx<BoxDecoration?> deco) {
return Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
child: GestureDetector(
onDoubleTap: () => _connect(peer.id),
child: Obx(
() => Container( () => Container(
decoration: deco.value, decoration: deco.value,
child: Column( child: Column(
@ -141,8 +250,9 @@ class _PeerCardState extends State<_PeerCard>
padding: EdgeInsets.fromLTRB(0, 4, 8, 4), padding: EdgeInsets.fromLTRB(0, 4, 8, 4),
child: CircleAvatar( child: CircleAvatar(
radius: 5, radius: 5,
backgroundColor: backgroundColor: peer.online
peer.online ? Colors.green : Colors.yellow)), ? Colors.green
: Colors.yellow)),
Text('${peer.id}') Text('${peer.id}')
]), ]),
InkWell( InkWell(
@ -160,6 +270,7 @@ class _PeerCardState extends State<_PeerCard>
], ],
), ),
), ),
)),
); );
} }

View File

@ -1230,8 +1230,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: f1d69e5d0531af947373ec26ae22808f08b1aac6 ref: "799ef079e87938c3f4340591b4330c2598f38bb9"
resolved-ref: f1d69e5d0531af947373ec26ae22808f08b1aac6 resolved-ref: "799ef079e87938c3f4340591b4330c2598f38bb9"
url: "https://github.com/Kingtous/rustdesk_window_manager" url: "https://github.com/Kingtous/rustdesk_window_manager"
source: git source: git
version: "0.2.5" version: "0.2.5"

View File

@ -61,7 +61,7 @@ dependencies:
window_manager: window_manager:
git: git:
url: https://github.com/Kingtous/rustdesk_window_manager url: https://github.com/Kingtous/rustdesk_window_manager
ref: f1d69e5d0531af947373ec26ae22808f08b1aac6 ref: 799ef079e87938c3f4340591b4330c2598f38bb9
desktop_multi_window: desktop_multi_window:
git: git:
url: https://github.com/Kingtous/rustdesk_desktop_multi_window url: https://github.com/Kingtous/rustdesk_desktop_multi_window