mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
Merge remote-tracking branch 'origin/master' into feat/x11/clipboard-file/init
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
|
||||
import 'package:flutter_hbb/models/peer_model.dart';
|
||||
|
||||
@@ -48,11 +49,18 @@ class UserPayload {
|
||||
};
|
||||
return map;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toGroupCacheJson() {
|
||||
final Map<String, dynamic> map = {
|
||||
'name': name,
|
||||
};
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
class PeerPayload {
|
||||
String id = '';
|
||||
String info = '';
|
||||
Map<String, dynamic> info = {};
|
||||
int? status;
|
||||
String user = '';
|
||||
String user_name = '';
|
||||
@@ -60,14 +68,45 @@ class PeerPayload {
|
||||
|
||||
PeerPayload.fromJson(Map<String, dynamic> json)
|
||||
: id = json['id'] ?? '',
|
||||
info = json['info'] ?? '',
|
||||
info = (json['info'] is Map<String, dynamic>) ? json['info'] : {},
|
||||
status = json['status'],
|
||||
user = json['user'] ?? '',
|
||||
user_name = json['user_name'] ?? '',
|
||||
note = json['note'] ?? '';
|
||||
|
||||
static Peer toPeer(PeerPayload p) {
|
||||
return Peer.fromJson({"id": p.id, "username": p.user_name});
|
||||
return Peer.fromJson({
|
||||
"id": p.id,
|
||||
'loginName': p.user_name,
|
||||
"username": p.info['username'] ?? '',
|
||||
"platform": _platform(p.info['os']),
|
||||
"hostname": p.info['device_name'],
|
||||
});
|
||||
}
|
||||
|
||||
static String? _platform(dynamic field) {
|
||||
if (field == null) {
|
||||
return null;
|
||||
}
|
||||
final fieldStr = field.toString();
|
||||
List<String> list = fieldStr.split(' / ');
|
||||
if (list.isEmpty) return null;
|
||||
final os = list[0];
|
||||
switch (os.toLowerCase()) {
|
||||
case 'windows':
|
||||
return kPeerPlatformWindows;
|
||||
case 'linux':
|
||||
return kPeerPlatformLinux;
|
||||
case 'macos':
|
||||
return kPeerPlatformMacOS;
|
||||
case 'android':
|
||||
return kPeerPlatformAndroid;
|
||||
default:
|
||||
if (fieldStr.toLowerCase().contains('linux')) {
|
||||
return kPeerPlatformLinux;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class _AddressBookState extends State<AddressBook> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Obx(() {
|
||||
if (gFFI.userModel.userName.value.isEmpty) {
|
||||
if (!gFFI.userModel.isLogin) {
|
||||
return Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: loginDialog, child: Text(translate("Login"))));
|
||||
@@ -49,11 +49,13 @@ class _AddressBookState extends State<AddressBook> {
|
||||
children: [
|
||||
// NOT use Offstage to wrap LinearProgressIndicator
|
||||
if (gFFI.abModel.retrying.value) LinearProgressIndicator(),
|
||||
_buildErrorBanner(
|
||||
buildErrorBanner(context,
|
||||
loading: gFFI.abModel.abLoading,
|
||||
err: gFFI.abModel.pullError,
|
||||
retry: null,
|
||||
close: () => gFFI.abModel.pullError.value = ''),
|
||||
_buildErrorBanner(
|
||||
buildErrorBanner(context,
|
||||
loading: gFFI.abModel.abLoading,
|
||||
err: gFFI.abModel.pushError,
|
||||
retry: () => gFFI.abModel.pushAb(isRetry: true),
|
||||
close: () => gFFI.abModel.pushError.value = ''),
|
||||
@@ -66,61 +68,6 @@ class _AddressBookState extends State<AddressBook> {
|
||||
}
|
||||
});
|
||||
|
||||
Widget _buildErrorBanner(
|
||||
{required RxString err,
|
||||
required Function? retry,
|
||||
required Function close}) {
|
||||
const double height = 25;
|
||||
return Obx(() => Offstage(
|
||||
offstage: !(!gFFI.abModel.abLoading.value && err.value.isNotEmpty),
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: height,
|
||||
color: Color.fromARGB(255, 253, 238, 235),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
FittedBox(
|
||||
child: Icon(
|
||||
Icons.info,
|
||||
color: Color.fromARGB(255, 249, 81, 81),
|
||||
),
|
||||
).marginAll(4),
|
||||
Flexible(
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Tooltip(
|
||||
message: translate(err.value),
|
||||
child: Text(
|
||||
translate(err.value),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
)).marginSymmetric(vertical: 2),
|
||||
),
|
||||
if (retry != null)
|
||||
InkWell(
|
||||
onTap: () {
|
||||
retry.call();
|
||||
},
|
||||
child: Text(
|
||||
translate("Retry"),
|
||||
style: TextStyle(color: MyTheme.accent),
|
||||
)).marginSymmetric(horizontal: 5),
|
||||
FittedBox(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
close.call();
|
||||
},
|
||||
child: Icon(Icons.close).marginSymmetric(horizontal: 5),
|
||||
),
|
||||
).marginAll(4)
|
||||
],
|
||||
),
|
||||
)).marginOnly(bottom: 14),
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildAddressBookDesktop() {
|
||||
return Row(
|
||||
children: [
|
||||
@@ -230,11 +177,10 @@ class _AddressBookState extends State<AddressBook> {
|
||||
return Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Obx(() => AddressBookPeersView(
|
||||
menuPadding: widget.menuPadding,
|
||||
// ignore: invalid_use_of_protected_member
|
||||
initPeers: gFFI.abModel.peers.value,
|
||||
))),
|
||||
child: AddressBookPeersView(
|
||||
menuPadding: widget.menuPadding,
|
||||
initPeers: gFFI.abModel.peers,
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -302,6 +302,53 @@ Future<String> changeDirectAccessPort(
|
||||
return controller.text;
|
||||
}
|
||||
|
||||
Future<String> changeAutoDisconnectTimeout(String old) async {
|
||||
final controller = TextEditingController(text: old);
|
||||
await gFFI.dialogManager.show((setState, close, context) {
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate("Timeout in minutes")),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 8.0),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
maxLines: null,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: InputDecoration(
|
||||
hintText: '10',
|
||||
isCollapsed: true,
|
||||
suffix: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: const Icon(Icons.clear, size: 16),
|
||||
onPressed: () => controller.clear())),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(
|
||||
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
|
||||
],
|
||||
controller: controller,
|
||||
autofocus: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
dialogButton("Cancel", onPressed: close, isOutline: true),
|
||||
dialogButton("OK", onPressed: () async {
|
||||
await bind.mainSetOption(
|
||||
key: 'auto-disconnect-timeout', value: controller.text);
|
||||
close();
|
||||
}),
|
||||
],
|
||||
onCancel: close,
|
||||
);
|
||||
});
|
||||
return controller.text;
|
||||
}
|
||||
|
||||
class DialogTextField extends StatelessWidget {
|
||||
final String title;
|
||||
final String? hintText;
|
||||
|
||||
@@ -29,49 +29,28 @@ class _MyGroupState extends State<MyGroup> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() {
|
||||
// use username to be same with ab
|
||||
if (gFFI.userModel.userName.value.isEmpty) {
|
||||
if (!gFFI.userModel.isLogin) {
|
||||
return Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: loginDialog, child: Text(translate("Login"))));
|
||||
}
|
||||
return buildBody(context);
|
||||
});
|
||||
}
|
||||
|
||||
Widget buildBody(BuildContext context) {
|
||||
return Obx(() {
|
||||
if (gFFI.groupModel.groupLoading.value) {
|
||||
} else if (gFFI.groupModel.groupLoading.value && gFFI.groupModel.emtpy) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
if (gFFI.groupModel.groupLoadError.isNotEmpty) {
|
||||
return _buildShowError(gFFI.groupModel.groupLoadError.value);
|
||||
}
|
||||
if (isDesktop) {
|
||||
return _buildDesktop();
|
||||
} else {
|
||||
return _buildMobile();
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
buildErrorBanner(context,
|
||||
loading: gFFI.groupModel.groupLoading,
|
||||
err: gFFI.groupModel.groupLoadError,
|
||||
retry: null,
|
||||
close: () => gFFI.groupModel.groupLoadError.value = ''),
|
||||
Expanded(child: isDesktop ? _buildDesktop() : _buildMobile())
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildShowError(String error) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(translate(error)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
gFFI.groupModel.pull();
|
||||
},
|
||||
child: Text(translate("Retry")))
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildDesktop() {
|
||||
return Row(
|
||||
children: [
|
||||
@@ -100,10 +79,9 @@ class _MyGroupState extends State<MyGroup> {
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Obx(() => MyGroupPeerView(
|
||||
child: MyGroupPeerView(
|
||||
menuPadding: widget.menuPadding,
|
||||
// ignore: invalid_use_of_protected_member
|
||||
initPeers: gFFI.groupModel.peersShow.value))),
|
||||
initPeers: gFFI.groupModel.peers)),
|
||||
)
|
||||
],
|
||||
);
|
||||
@@ -133,10 +111,9 @@ class _MyGroupState extends State<MyGroup> {
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Obx(() => MyGroupPeerView(
|
||||
child: MyGroupPeerView(
|
||||
menuPadding: widget.menuPadding,
|
||||
// ignore: invalid_use_of_protected_member
|
||||
initPeers: gFFI.groupModel.peersShow.value))),
|
||||
initPeers: gFFI.groupModel.peers)),
|
||||
)
|
||||
],
|
||||
);
|
||||
@@ -195,6 +172,7 @@ class _MyGroupState extends State<MyGroup> {
|
||||
}, child: Obx(
|
||||
() {
|
||||
bool selected = selectedUser.value == username;
|
||||
final isMe = username == gFFI.userModel.userName.value;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? MyTheme.color(context).highlight : null,
|
||||
@@ -208,7 +186,7 @@ class _MyGroupState extends State<MyGroup> {
|
||||
children: [
|
||||
Icon(Icons.person_rounded, color: Colors.grey, size: 16)
|
||||
.marginOnly(right: 4),
|
||||
Expanded(child: Text(username)),
|
||||
Expanded(child: Text(isMe ? translate('Me') : username)),
|
||||
],
|
||||
).paddingSymmetric(vertical: 4),
|
||||
),
|
||||
|
||||
@@ -727,7 +727,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
MenuEntryBase<String> _unrememberPasswordAction(String id) {
|
||||
return MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Unremember Password'),
|
||||
translate('Forget Password'),
|
||||
style: style,
|
||||
),
|
||||
proc: () async {
|
||||
@@ -1093,7 +1093,7 @@ class MyGroupPeerCard extends BasePeerCard {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
}
|
||||
// menuItems.add(await _openNewConnInOptAction(peer.id));
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
// menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (peer.platform == 'Windows') {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
@@ -1101,9 +1101,14 @@ class MyGroupPeerCard extends BasePeerCard {
|
||||
menuItems.add(_createShortCutAction(peer.id));
|
||||
}
|
||||
menuItems.add(MenuEntryDivider());
|
||||
menuItems.add(_renameAction(peer.id));
|
||||
if (await bind.mainPeerHasPassword(id: peer.id)) {
|
||||
menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
// menuItems.add(_renameAction(peer.id));
|
||||
// if (await bind.mainPeerHasPassword(id: peer.id)) {
|
||||
// menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
// }
|
||||
if (gFFI.userModel.userName.isNotEmpty) {
|
||||
if (!gFFI.abModel.idContainBy(peer.id)) {
|
||||
menuItems.add(_addToAb(peer));
|
||||
}
|
||||
}
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/widgets/address_book.dart';
|
||||
import 'package:flutter_hbb/common/widgets/dialog.dart';
|
||||
@@ -6,6 +9,9 @@ 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:flutter_hbb/desktop/widgets/popup_menu.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart'
|
||||
as mod_menu;
|
||||
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||
import 'package:flutter_hbb/models/ab_model.dart';
|
||||
|
||||
import 'package:flutter_hbb/models/peer_tab_model.dart';
|
||||
@@ -61,6 +67,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
({dynamic hint}) => gFFI.groupModel.pull(force: hint == null),
|
||||
),
|
||||
];
|
||||
RelativeRect? mobileTabContextMenuPos;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -100,9 +107,15 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
child: selectionWrap(Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(child: _createSwitchBar(context)),
|
||||
Expanded(
|
||||
child:
|
||||
visibleContextMenuListener(_createSwitchBar(context))),
|
||||
const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13),
|
||||
_createRefresh(),
|
||||
_createRefresh(
|
||||
index: PeerTabIndex.ab, loading: gFFI.abModel.abLoading),
|
||||
_createRefresh(
|
||||
index: PeerTabIndex.group,
|
||||
loading: gFFI.groupModel.groupLoading),
|
||||
_createMultiSelection(),
|
||||
Offstage(
|
||||
offstage: !isDesktop,
|
||||
@@ -145,7 +158,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
return ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
children: model.indexs.map((t) {
|
||||
children: model.visibleIndexs.map((t) {
|
||||
final selected = model.currentTab == t;
|
||||
final color = selected
|
||||
? MyTheme.tabbar(context).selectedTextColor
|
||||
@@ -161,11 +174,13 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
));
|
||||
return Obx(() => InkWell(
|
||||
child: Container(
|
||||
decoration:
|
||||
selected ? decoBorder : (hover.value ? deco : null),
|
||||
decoration: (hover.value
|
||||
? (selected ? decoBorder : deco)
|
||||
: (selected ? decoBorder : null)),
|
||||
child: Tooltip(
|
||||
message:
|
||||
model.tabTooltip(t, gFFI.groupModel.groupName.value),
|
||||
preferBelow: false,
|
||||
message: model.tabTooltip(t),
|
||||
onTriggered: isMobile ? mobileShowTabVisibilityMenu : null,
|
||||
child: Icon(model.tabIcon(t), color: color),
|
||||
).paddingSymmetric(horizontal: 4),
|
||||
).paddingSymmetric(horizontal: 4),
|
||||
@@ -182,14 +197,15 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
Widget _createPeersView() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
Widget child;
|
||||
if (model.indexs.isEmpty) {
|
||||
child = Center(
|
||||
child: Text(translate('Right click to select tabs')),
|
||||
);
|
||||
if (model.visibleIndexs.isEmpty) {
|
||||
child = visibleContextMenuListener(Row(
|
||||
children: [Expanded(child: InkWell())],
|
||||
));
|
||||
} else {
|
||||
if (model.indexs.contains(model.currentTab)) {
|
||||
if (model.visibleIndexs.contains(model.currentTab)) {
|
||||
child = entries[model.currentTab].widget;
|
||||
} else {
|
||||
debugPrint("should not happen! currentTab not in visibleIndexs");
|
||||
Future.delayed(Duration.zero, () {
|
||||
model.setCurrentTab(model.indexs[0]);
|
||||
});
|
||||
@@ -200,17 +216,19 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
child: child.marginSymmetric(vertical: isDesktop ? 12.0 : 6.0));
|
||||
}
|
||||
|
||||
Widget _createRefresh() {
|
||||
Widget _createRefresh(
|
||||
{required PeerTabIndex index, required RxBool loading}) {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||
return Offstage(
|
||||
offstage: gFFI.peerTabModel.currentTab != PeerTabIndex.ab.index,
|
||||
offstage: model.currentTab != index.index,
|
||||
child: RefreshWidget(
|
||||
onPressed: () {
|
||||
if (gFFI.peerTabModel.currentTab < entries.length) {
|
||||
entries[gFFI.peerTabModel.currentTab].load();
|
||||
}
|
||||
},
|
||||
spinning: gFFI.abModel.abLoading,
|
||||
spinning: loading,
|
||||
child: RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: Tooltip(
|
||||
@@ -268,6 +286,94 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
);
|
||||
}
|
||||
|
||||
void mobileShowTabVisibilityMenu() {
|
||||
final model = gFFI.peerTabModel;
|
||||
final items = List<PopupMenuItem>.empty(growable: true);
|
||||
for (int i = 0; i < model.tabNames.length; i++) {
|
||||
items.add(PopupMenuItem(
|
||||
height: kMinInteractiveDimension * 0.8,
|
||||
onTap: () => model.setTabVisible(i, !model.isVisible[i]),
|
||||
child: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: model.isVisible[i],
|
||||
onChanged: (_) {
|
||||
model.setTabVisible(i, !model.isVisible[i]);
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}),
|
||||
Expanded(child: Text(model.tabTooltip(i))),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
if (mobileTabContextMenuPos != null) {
|
||||
showMenu(
|
||||
context: context, position: mobileTabContextMenuPos!, items: items);
|
||||
}
|
||||
}
|
||||
|
||||
Widget visibleContextMenuListener(Widget child) {
|
||||
if (isMobile) {
|
||||
return GestureDetector(
|
||||
onLongPressDown: (e) {
|
||||
final x = e.globalPosition.dx;
|
||||
final y = e.globalPosition.dy;
|
||||
mobileTabContextMenuPos = RelativeRect.fromLTRB(x, y, x, y);
|
||||
},
|
||||
onLongPressUp: () {
|
||||
mobileShowTabVisibilityMenu();
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
} else {
|
||||
return Listener(
|
||||
onPointerDown: (e) {
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) {
|
||||
return;
|
||||
}
|
||||
if (e.buttons == 2) {
|
||||
showRightMenu(
|
||||
(CancelFunc cancelFunc) {
|
||||
return visibleContextMenu(cancelFunc);
|
||||
},
|
||||
target: e.position,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: child);
|
||||
}
|
||||
}
|
||||
|
||||
Widget visibleContextMenu(CancelFunc cancelFunc) {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
final menu = List<MenuEntrySwitch>.empty(growable: true);
|
||||
for (int i = 0; i < model.tabNames.length; i++) {
|
||||
menu.add(MenuEntrySwitch(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: model.tabTooltip(i),
|
||||
getter: () async {
|
||||
return model.isVisible[i];
|
||||
},
|
||||
setter: (show) async {
|
||||
model.setTabVisible(i, show);
|
||||
cancelFunc();
|
||||
}));
|
||||
}
|
||||
return mod_menu.PopupMenu(
|
||||
items: menu
|
||||
.map((entry) => entry.build(
|
||||
context,
|
||||
const MenuConfig(
|
||||
commonColor: MyTheme.accent,
|
||||
height: 20.0,
|
||||
dividerHeight: 12.0,
|
||||
)))
|
||||
.expand((i) => i)
|
||||
.toList());
|
||||
}
|
||||
|
||||
Widget createMultiSelectionBar() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
return Row(
|
||||
@@ -286,6 +392,9 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
|
||||
Widget deleteSelection() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
if (model.currentTab == PeerTabIndex.group.index) {
|
||||
return Offstage();
|
||||
}
|
||||
return _hoverAction(
|
||||
context: context,
|
||||
onTap: () {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:dynamic_layouts/dynamic_layouts.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -35,6 +36,7 @@ class LoadEvent {
|
||||
static const String favorite = 'load_fav_peers';
|
||||
static const String lan = 'load_lan_peers';
|
||||
static const String addressBook = 'load_address_book_peers';
|
||||
static const String group = 'load_group_peers';
|
||||
}
|
||||
|
||||
/// for peer search text, global obs value
|
||||
@@ -176,26 +178,29 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
|
||||
if (snapshot.hasData) {
|
||||
final peers = snapshot.data!;
|
||||
gFFI.peerTabModel.setCurrentTabCachedPeers(peers);
|
||||
final cards = <Widget>[];
|
||||
for (final peer in peers) {
|
||||
final visibilityChild = VisibilityDetector(
|
||||
key: ValueKey(_cardId(peer.id)),
|
||||
onVisibilityChanged: onVisibilityChanged,
|
||||
child: widget.peerCardBuilder(peer),
|
||||
);
|
||||
cards.add(isDesktop
|
||||
? Obx(
|
||||
() => SizedBox(
|
||||
width: 220,
|
||||
height:
|
||||
peerCardUiType.value == PeerUiType.grid ? 140 : 42,
|
||||
child: visibilityChild,
|
||||
),
|
||||
)
|
||||
: SizedBox(width: mobileWidth, child: visibilityChild));
|
||||
}
|
||||
final child =
|
||||
Wrap(spacing: space, runSpacing: space, children: cards);
|
||||
final child = DynamicGridView.builder(
|
||||
gridDelegate: SliverGridDelegateWithWrapping(
|
||||
mainAxisSpacing: space / 2, crossAxisSpacing: space),
|
||||
itemCount: peers.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final visibilityChild = VisibilityDetector(
|
||||
key: ValueKey(_cardId(peers[index].id)),
|
||||
onVisibilityChanged: onVisibilityChanged,
|
||||
child: widget.peerCardBuilder(peers[index]),
|
||||
);
|
||||
return isDesktop
|
||||
? Obx(
|
||||
() => SizedBox(
|
||||
width: 220,
|
||||
height: peerCardUiType.value == PeerUiType.grid
|
||||
? 140
|
||||
: 42,
|
||||
child: visibilityChild,
|
||||
),
|
||||
)
|
||||
: SizedBox(width: mobileWidth, child: visibilityChild);
|
||||
},
|
||||
);
|
||||
if (updateEvent == UpdateEvent.load) {
|
||||
_curPeers.clear();
|
||||
_curPeers.addAll(peers.map((e) => e.id));
|
||||
@@ -312,7 +317,7 @@ abstract class BasePeersView extends StatelessWidget {
|
||||
final String loadEvent;
|
||||
final PeerFilter? peerFilter;
|
||||
final PeerCardBuilder peerCardBuilder;
|
||||
final List<Peer> initPeers;
|
||||
final RxList<Peer>? initPeers;
|
||||
|
||||
const BasePeersView({
|
||||
Key? key,
|
||||
@@ -326,7 +331,7 @@ abstract class BasePeersView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _PeersView(
|
||||
peers: Peers(name: name, loadEvent: loadEvent, peers: initPeers),
|
||||
peers: Peers(name: name, loadEvent: loadEvent, initPeers: initPeers),
|
||||
peerFilter: peerFilter,
|
||||
peerCardBuilder: peerCardBuilder);
|
||||
}
|
||||
@@ -343,7 +348,7 @@ class RecentPeersView extends BasePeersView {
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: [],
|
||||
initPeers: null,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -365,7 +370,7 @@ class FavoritePeersView extends BasePeersView {
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: [],
|
||||
initPeers: null,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -387,7 +392,7 @@ class DiscoveredPeersView extends BasePeersView {
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: [],
|
||||
initPeers: null,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -403,7 +408,7 @@ class AddressBookPeersView extends BasePeersView {
|
||||
{Key? key,
|
||||
EdgeInsets? menuPadding,
|
||||
ScrollController? scrollController,
|
||||
required List<Peer> initPeers})
|
||||
required RxList<Peer> initPeers})
|
||||
: super(
|
||||
key: key,
|
||||
name: 'address book peer',
|
||||
@@ -435,11 +440,11 @@ class MyGroupPeerView extends BasePeersView {
|
||||
{Key? key,
|
||||
EdgeInsets? menuPadding,
|
||||
ScrollController? scrollController,
|
||||
required List<Peer> initPeers})
|
||||
required RxList<Peer> initPeers})
|
||||
: super(
|
||||
key: key,
|
||||
name: 'my group peer',
|
||||
loadEvent: 'load_my_group_peers',
|
||||
name: 'group peer',
|
||||
loadEvent: LoadEvent.group,
|
||||
peerFilter: filter,
|
||||
peerCardBuilder: (Peer peer) => MyGroupPeerCard(
|
||||
peer: peer,
|
||||
@@ -450,12 +455,12 @@ class MyGroupPeerView extends BasePeersView {
|
||||
|
||||
static bool filter(Peer peer) {
|
||||
if (gFFI.groupModel.searchUserText.isNotEmpty) {
|
||||
if (!peer.username.contains(gFFI.groupModel.searchUserText)) {
|
||||
if (!peer.loginName.contains(gFFI.groupModel.searchUserText)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (gFFI.groupModel.selectedUser.isNotEmpty) {
|
||||
if (gFFI.groupModel.selectedUser.value != peer.username) {
|
||||
if (gFFI.groupModel.selectedUser.value != peer.loginName) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user