peer card batch operation
Signed-off-by: 21pages <pages21@163.com>
This commit is contained in:
parent
bdc5cded22
commit
5a0865559c
@ -9,6 +9,7 @@ import 'package:get/get.dart';
|
||||
import '../../common.dart';
|
||||
import '../../models/model.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
import 'address_book.dart';
|
||||
|
||||
void clientClose(SessionID sessionId, OverlayDialogManager dialogManager) {
|
||||
msgBox(sessionId, 'info', 'Close', 'Are you sure to close the connection?',
|
||||
@ -1350,3 +1351,98 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
|
||||
);
|
||||
msgBoxCommon(ffi.dialogManager, 'Custom Image Quality', content, [btnClose]);
|
||||
}
|
||||
|
||||
void deletePeerConfirmDialog(Function onSubmit) async {
|
||||
gFFI.dialogManager.show(
|
||||
(setState, close, context) {
|
||||
submit() async {
|
||||
await onSubmit();
|
||||
close();
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.delete_rounded,
|
||||
color: Colors.red,
|
||||
),
|
||||
Text(translate('Delete')).paddingOnly(
|
||||
left: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
content: SizedBox.shrink(),
|
||||
actions: [
|
||||
dialogButton(
|
||||
"Cancel",
|
||||
icon: Icon(Icons.close_rounded),
|
||||
onPressed: close,
|
||||
isOutline: true,
|
||||
),
|
||||
dialogButton(
|
||||
"OK",
|
||||
icon: Icon(Icons.done_rounded),
|
||||
onPressed: submit,
|
||||
),
|
||||
],
|
||||
onSubmit: submit,
|
||||
onCancel: close,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void editAbTagDialog(
|
||||
List<dynamic> currentTags, Function(List<dynamic>) onSubmit) {
|
||||
var isInProgress = false;
|
||||
|
||||
final tags = List.of(gFFI.abModel.tags);
|
||||
var selectedTag = currentTags.obs;
|
||||
|
||||
gFFI.dialogManager.show((setState, close, context) {
|
||||
submit() async {
|
||||
setState(() {
|
||||
isInProgress = true;
|
||||
});
|
||||
await onSubmit(selectedTag);
|
||||
close();
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate("Edit Tag")),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Wrap(
|
||||
children: tags
|
||||
.map((e) => AddressBookTag(
|
||||
name: e,
|
||||
tags: selectedTag,
|
||||
onTap: () {
|
||||
if (selectedTag.contains(e)) {
|
||||
selectedTag.remove(e);
|
||||
} else {
|
||||
selectedTag.add(e);
|
||||
}
|
||||
},
|
||||
showActionMenu: false))
|
||||
.toList(growable: false),
|
||||
),
|
||||
),
|
||||
Offstage(
|
||||
offstage: !isInProgress, child: const LinearProgressIndicator())
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
dialogButton("Cancel", onPressed: close, isOutline: true),
|
||||
dialogButton("OK", onPressed: submit),
|
||||
],
|
||||
onSubmit: submit,
|
||||
onCancel: close,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -3,8 +3,11 @@ import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common/widgets/address_book.dart';
|
||||
import 'package:flutter_hbb/common/widgets/dialog.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/models/peer_tab_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../common/formatter/id_formatter.dart';
|
||||
@ -58,17 +61,21 @@ class _PeerCardState extends State<_PeerCard>
|
||||
final peer = super.widget.peer;
|
||||
final name =
|
||||
'${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}';
|
||||
|
||||
final PeerTabModel peerTabModel = Provider.of(context);
|
||||
final selected = peerTabModel.isPeerSelected(peer.id);
|
||||
return Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 2),
|
||||
child: GestureDetector(
|
||||
onTap: !isWebDesktop ? () => connect(context, peer.id) : null,
|
||||
onTap: () {
|
||||
if (peerTabModel.multiSelectionMode) {
|
||||
peerTabModel.onPeerCardTap(peer);
|
||||
} else {
|
||||
if (!isWebDesktop) connect(context, peer.id);
|
||||
}
|
||||
},
|
||||
onDoubleTap: isWebDesktop ? () => connect(context, peer.id) : null,
|
||||
onLongPressStart: (details) {
|
||||
final x = details.globalPosition.dx;
|
||||
final y = details.globalPosition.dy;
|
||||
_menuPos = RelativeRect.fromLTRB(x, y, x, y);
|
||||
_showPeerMenu(peer.id);
|
||||
onLongPress: () {
|
||||
peerTabModel.togglePeerSelect(peer);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 12, top: 8, bottom: 8),
|
||||
@ -97,24 +104,30 @@ class _PeerCardState extends State<_PeerCard>
|
||||
],
|
||||
).paddingOnly(left: 8.0),
|
||||
),
|
||||
InkWell(
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
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(peer.id);
|
||||
})
|
||||
selected
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: checkBox(),
|
||||
)
|
||||
: InkWell(
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
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(peer.id);
|
||||
}),
|
||||
],
|
||||
),
|
||||
)));
|
||||
}
|
||||
|
||||
Widget _buildDesktop() {
|
||||
final PeerTabModel peerTabModel = Provider.of(context);
|
||||
final peer = super.widget.peer;
|
||||
var deco = Rx<BoxDecoration?>(
|
||||
BoxDecoration(
|
||||
@ -144,7 +157,18 @@ class _PeerCardState extends State<_PeerCard>
|
||||
);
|
||||
},
|
||||
child: GestureDetector(
|
||||
onDoubleTap: () => widget.connect(context, peer.id),
|
||||
onDoubleTap: peerTabModel.multiSelectionMode
|
||||
? null
|
||||
: () => widget.connect(context, peer.id),
|
||||
onLongPress: () {
|
||||
peerTabModel.togglePeerSelect(peer);
|
||||
},
|
||||
onSecondaryTapDown: (_) {
|
||||
peerTabModel.togglePeerSelect(peer);
|
||||
},
|
||||
onTap: peerTabModel.multiSelectionMode
|
||||
? () => peerTabModel.onPeerCardTap(peer)
|
||||
: null,
|
||||
child: Obx(() => peerCardUiType.value == PeerUiType.grid
|
||||
? _buildPeerCard(context, peer, deco)
|
||||
: _buildPeerTile(context, peer, deco))),
|
||||
@ -153,6 +177,8 @@ class _PeerCardState extends State<_PeerCard>
|
||||
|
||||
Widget _buildPeerTile(
|
||||
BuildContext context, Peer peer, Rx<BoxDecoration?> deco) {
|
||||
final PeerTabModel peerTabModel = Provider.of(context);
|
||||
final selected = peerTabModel.isPeerSelected(peer.id);
|
||||
final name =
|
||||
'${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}';
|
||||
final greyStyle = TextStyle(
|
||||
@ -212,7 +238,7 @@ class _PeerCardState extends State<_PeerCard>
|
||||
],
|
||||
).marginOnly(top: 2),
|
||||
),
|
||||
_actionMore(peer),
|
||||
selected ? checkBox() : _actionMore(peer),
|
||||
],
|
||||
).paddingOnly(left: 10.0, top: 3.0),
|
||||
),
|
||||
@ -225,6 +251,8 @@ class _PeerCardState extends State<_PeerCard>
|
||||
|
||||
Widget _buildPeerCard(
|
||||
BuildContext context, Peer peer, Rx<BoxDecoration?> deco) {
|
||||
final PeerTabModel peerTabModel = Provider.of(context);
|
||||
final selected = peerTabModel.isPeerSelected(peer.id);
|
||||
final name =
|
||||
'${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}';
|
||||
return Card(
|
||||
@ -294,7 +322,7 @@ class _PeerCardState extends State<_PeerCard>
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
)),
|
||||
]).paddingSymmetric(vertical: 8)),
|
||||
_actionMore(peer),
|
||||
selected ? checkBox() : _actionMore(peer),
|
||||
],
|
||||
).paddingSymmetric(horizontal: 12.0),
|
||||
)
|
||||
@ -306,6 +334,13 @@ class _PeerCardState extends State<_PeerCard>
|
||||
);
|
||||
}
|
||||
|
||||
Widget checkBox() {
|
||||
return Icon(
|
||||
Icons.check_box,
|
||||
color: MyTheme.accent,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _actionMore(Peer peer) => Listener(
|
||||
onPointerDown: (e) {
|
||||
final x = e.position.dx;
|
||||
@ -332,9 +367,11 @@ class _PeerCardState extends State<_PeerCard>
|
||||
|
||||
abstract class BasePeerCard extends StatelessWidget {
|
||||
final Peer peer;
|
||||
final PeerTabIndex tab;
|
||||
final EdgeInsets? menuPadding;
|
||||
|
||||
BasePeerCard({required this.peer, this.menuPadding, Key? key})
|
||||
BasePeerCard(
|
||||
{required this.peer, required this.tab, this.menuPadding, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
@ -524,9 +561,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
@protected
|
||||
MenuEntryBase<String> _removeAction(
|
||||
String id, Future<void> Function() reloadFunc,
|
||||
{bool isLan = false}) {
|
||||
MenuEntryBase<String> _removeAction(String id) {
|
||||
return MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Row(
|
||||
children: [
|
||||
@ -545,7 +580,33 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
proc: () {
|
||||
_delete(id, isLan, reloadFunc);
|
||||
onSubmit() async {
|
||||
switch (tab) {
|
||||
case PeerTabIndex.recent:
|
||||
await bind.mainRemovePeer(id: id);
|
||||
await bind.mainLoadRecentPeers();
|
||||
break;
|
||||
case PeerTabIndex.fav:
|
||||
final favs = (await bind.mainGetFav()).toList();
|
||||
if (favs.remove(id)) {
|
||||
await bind.mainStoreFav(favs: favs);
|
||||
await bind.mainLoadFavPeers();
|
||||
}
|
||||
break;
|
||||
case PeerTabIndex.lan:
|
||||
await bind.mainRemoveDiscovered(id: id);
|
||||
await bind.mainLoadLanPeers();
|
||||
break;
|
||||
case PeerTabIndex.ab:
|
||||
gFFI.abModel.deletePeer(id);
|
||||
await gFFI.abModel.pushAb();
|
||||
break;
|
||||
case PeerTabIndex.group:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
deletePeerConfirmDialog(onSubmit);
|
||||
},
|
||||
padding: menuPadding,
|
||||
dismissOnClicked: true,
|
||||
@ -721,62 +782,15 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
|
||||
@protected
|
||||
void _update();
|
||||
|
||||
void _delete(String id, bool isLan, Function reloadFunc) async {
|
||||
gFFI.dialogManager.show(
|
||||
(setState, close, context) {
|
||||
submit() async {
|
||||
if (isLan) {
|
||||
await bind.mainRemoveDiscovered(id: id);
|
||||
} else {
|
||||
final favs = (await bind.mainGetFav()).toList();
|
||||
if (favs.remove(id)) {
|
||||
await bind.mainStoreFav(favs: favs);
|
||||
}
|
||||
await bind.mainRemovePeer(id: id);
|
||||
}
|
||||
await reloadFunc();
|
||||
close();
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.delete_rounded,
|
||||
color: Colors.red,
|
||||
),
|
||||
Text(translate('Delete')).paddingOnly(
|
||||
left: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
content: SizedBox.shrink(),
|
||||
actions: [
|
||||
dialogButton(
|
||||
"Cancel",
|
||||
icon: Icon(Icons.close_rounded),
|
||||
onPressed: close,
|
||||
isOutline: true,
|
||||
),
|
||||
dialogButton(
|
||||
"OK",
|
||||
icon: Icon(Icons.done_rounded),
|
||||
onPressed: submit,
|
||||
),
|
||||
],
|
||||
onSubmit: submit,
|
||||
onCancel: close,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RecentPeerCard extends BasePeerCard {
|
||||
RecentPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
|
||||
: super(peer: peer, menuPadding: menuPadding, key: key);
|
||||
: super(
|
||||
peer: peer,
|
||||
tab: PeerTabIndex.recent,
|
||||
menuPadding: menuPadding,
|
||||
key: key);
|
||||
|
||||
@override
|
||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||
@ -817,9 +831,7 @@ class RecentPeerCard extends BasePeerCard {
|
||||
}
|
||||
|
||||
menuItems.add(MenuEntryDivider());
|
||||
menuItems.add(_removeAction(peer.id, () async {
|
||||
await bind.mainLoadRecentPeers();
|
||||
}));
|
||||
menuItems.add(_removeAction(peer.id));
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
@ -830,7 +842,11 @@ class RecentPeerCard extends BasePeerCard {
|
||||
|
||||
class FavoritePeerCard extends BasePeerCard {
|
||||
FavoritePeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
|
||||
: super(peer: peer, menuPadding: menuPadding, key: key);
|
||||
: super(
|
||||
peer: peer,
|
||||
tab: PeerTabIndex.fav,
|
||||
menuPadding: menuPadding,
|
||||
key: key);
|
||||
|
||||
@override
|
||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||
@ -865,9 +881,7 @@ class FavoritePeerCard extends BasePeerCard {
|
||||
}
|
||||
|
||||
menuItems.add(MenuEntryDivider());
|
||||
menuItems.add(_removeAction(peer.id, () async {
|
||||
await bind.mainLoadFavPeers();
|
||||
}));
|
||||
menuItems.add(_removeAction(peer.id));
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
@ -878,7 +892,11 @@ class FavoritePeerCard extends BasePeerCard {
|
||||
|
||||
class DiscoveredPeerCard extends BasePeerCard {
|
||||
DiscoveredPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
|
||||
: super(peer: peer, menuPadding: menuPadding, key: key);
|
||||
: super(
|
||||
peer: peer,
|
||||
tab: PeerTabIndex.lan,
|
||||
menuPadding: menuPadding,
|
||||
key: key);
|
||||
|
||||
@override
|
||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||
@ -915,11 +933,7 @@ class DiscoveredPeerCard extends BasePeerCard {
|
||||
}
|
||||
|
||||
menuItems.add(MenuEntryDivider());
|
||||
menuItems.add(
|
||||
_removeAction(peer.id, () async {
|
||||
await bind.mainLoadLanPeers();
|
||||
}, isLan: true),
|
||||
);
|
||||
menuItems.add(_removeAction(peer.id));
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
@ -930,7 +944,11 @@ class DiscoveredPeerCard extends BasePeerCard {
|
||||
|
||||
class AddressBookPeerCard extends BasePeerCard {
|
||||
AddressBookPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
|
||||
: super(peer: peer, menuPadding: menuPadding, key: key);
|
||||
: super(
|
||||
peer: peer,
|
||||
tab: PeerTabIndex.ab,
|
||||
menuPadding: menuPadding,
|
||||
key: key);
|
||||
|
||||
@override
|
||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||
@ -959,7 +977,7 @@ class AddressBookPeerCard extends BasePeerCard {
|
||||
}
|
||||
|
||||
menuItems.add(MenuEntryDivider());
|
||||
menuItems.add(_removeAction(peer.id, () async {}));
|
||||
menuItems.add(_removeAction(peer.id));
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
@ -967,27 +985,6 @@ class AddressBookPeerCard extends BasePeerCard {
|
||||
@override
|
||||
void _update() => gFFI.abModel.pullAb();
|
||||
|
||||
@protected
|
||||
@override
|
||||
MenuEntryBase<String> _removeAction(
|
||||
String id, Future<void> Function() reloadFunc,
|
||||
{bool isLan = false}) {
|
||||
return MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Remove'),
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
() async {
|
||||
gFFI.abModel.deletePeer(id);
|
||||
await gFFI.abModel.pushAb();
|
||||
}();
|
||||
},
|
||||
padding: super.menuPadding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
MenuEntryBase<String> _editTagAction(String id) {
|
||||
return MenuEntryButton<String>(
|
||||
@ -996,70 +993,24 @@ class AddressBookPeerCard extends BasePeerCard {
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
_abEditTag(id);
|
||||
editAbTagDialog(gFFI.abModel.getPeerTags(id), (selectedTag) async {
|
||||
gFFI.abModel.changeTagForPeer(id, selectedTag);
|
||||
await gFFI.abModel.pushAb();
|
||||
});
|
||||
},
|
||||
padding: super.menuPadding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
|
||||
void _abEditTag(String id) {
|
||||
var isInProgress = false;
|
||||
|
||||
final tags = List.of(gFFI.abModel.tags);
|
||||
var selectedTag = gFFI.abModel.getPeerTags(id).obs;
|
||||
|
||||
gFFI.dialogManager.show((setState, close, context) {
|
||||
submit() async {
|
||||
setState(() {
|
||||
isInProgress = true;
|
||||
});
|
||||
gFFI.abModel.changeTagForPeer(id, selectedTag);
|
||||
await gFFI.abModel.pushAb();
|
||||
close();
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate("Edit Tag")),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Wrap(
|
||||
children: tags
|
||||
.map((e) => AddressBookTag(
|
||||
name: e,
|
||||
tags: selectedTag,
|
||||
onTap: () {
|
||||
if (selectedTag.contains(e)) {
|
||||
selectedTag.remove(e);
|
||||
} else {
|
||||
selectedTag.add(e);
|
||||
}
|
||||
},
|
||||
showActionMenu: false))
|
||||
.toList(growable: false),
|
||||
),
|
||||
),
|
||||
Offstage(
|
||||
offstage: !isInProgress, child: const LinearProgressIndicator())
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
dialogButton("Cancel", onPressed: close, isOutline: true),
|
||||
dialogButton("OK", onPressed: submit),
|
||||
],
|
||||
onSubmit: submit,
|
||||
onCancel: close,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class MyGroupPeerCard extends BasePeerCard {
|
||||
MyGroupPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
|
||||
: super(peer: peer, menuPadding: menuPadding, key: key);
|
||||
: super(
|
||||
peer: peer,
|
||||
tab: PeerTabIndex.group,
|
||||
menuPadding: menuPadding,
|
||||
key: key);
|
||||
|
||||
@override
|
||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/widgets/address_book.dart';
|
||||
import 'package:flutter_hbb/common/widgets/dialog.dart';
|
||||
import 'package:flutter_hbb/common/widgets/my_group.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peers_view.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peer_card.dart';
|
||||
@ -83,6 +84,11 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
Widget selectionWrap(Widget widget) {
|
||||
return model.multiSelectionMode ? createMultiSelectionBar() : widget;
|
||||
}
|
||||
|
||||
return Column(
|
||||
textBaseline: TextBaseline.ideographic,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -91,7 +97,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
height: 32,
|
||||
child: Container(
|
||||
padding: isDesktop ? null : EdgeInsets.symmetric(horizontal: 2),
|
||||
child: Row(
|
||||
child: selectionWrap(Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(child: _createSwitchBar(context)),
|
||||
@ -127,7 +133,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
).marginOnly(left: 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
_createPeersView(),
|
||||
@ -251,6 +257,167 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget createMultiSelectionBar() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
return Row(
|
||||
children: [
|
||||
deleteSelection(),
|
||||
addSelectionToFav(),
|
||||
addSelectionToAb(),
|
||||
editSelectionTags(),
|
||||
Expanded(child: Container()),
|
||||
selectionCount(model.selectedPeers.length),
|
||||
selectAll(),
|
||||
closeSelection(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget deleteSelection() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
onSubmit() async {
|
||||
final peers = model.selectedPeers;
|
||||
switch (model.currentTab) {
|
||||
case 0:
|
||||
peers.map((p) async {
|
||||
await bind.mainRemovePeer(id: p.id);
|
||||
}).toList();
|
||||
await bind.mainLoadRecentPeers();
|
||||
break;
|
||||
case 1:
|
||||
final favs = (await bind.mainGetFav()).toList();
|
||||
peers.map((p) {
|
||||
favs.remove(p.id);
|
||||
}).toList();
|
||||
await bind.mainStoreFav(favs: favs);
|
||||
await bind.mainLoadFavPeers();
|
||||
break;
|
||||
case 2:
|
||||
peers.map((p) async {
|
||||
await bind.mainRemoveDiscovered(id: p.id);
|
||||
}).toList();
|
||||
await bind.mainLoadLanPeers();
|
||||
break;
|
||||
case 3:
|
||||
gFFI.abModel.deletePeers(peers.map((p) => p.id).toList());
|
||||
await gFFI.abModel.pushAb();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
gFFI.peerTabModel.closeSelection();
|
||||
}
|
||||
|
||||
deletePeerConfirmDialog(onSubmit);
|
||||
},
|
||||
child: Tooltip(
|
||||
message: translate('Delete'),
|
||||
child: Icon(Icons.delete, color: Colors.red)));
|
||||
}
|
||||
|
||||
Widget addSelectionToFav() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
return Offstage(
|
||||
offstage:
|
||||
model.currentTab != PeerTabIndex.recent.index, // show based on recent
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
final peers = model.selectedPeers;
|
||||
final favs = (await bind.mainGetFav()).toList();
|
||||
for (var p in peers) {
|
||||
if (!favs.contains(p.id)) {
|
||||
favs.add(p.id);
|
||||
}
|
||||
}
|
||||
await bind.mainStoreFav(favs: favs);
|
||||
gFFI.peerTabModel.closeSelection();
|
||||
},
|
||||
child: Tooltip(
|
||||
message: translate('Add to Favorites'),
|
||||
child: Icon(model.icons[PeerTabIndex.fav.index]))
|
||||
.marginOnly(left: isMobile ? 15 : 10),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget addSelectionToAb() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
return Offstage(
|
||||
offstage:
|
||||
!gFFI.userModel.isLogin || model.currentTab == PeerTabIndex.ab.index,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
final peers = model.selectedPeers;
|
||||
gFFI.abModel.addPeers(peers);
|
||||
gFFI.abModel.pushAb();
|
||||
gFFI.peerTabModel.closeSelection();
|
||||
},
|
||||
child: Tooltip(
|
||||
message: translate('Add to Address Book'),
|
||||
child: Icon(model.icons[PeerTabIndex.ab.index]))
|
||||
.marginOnly(left: isMobile ? 15 : 10),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget editSelectionTags() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
return Offstage(
|
||||
offstage: !gFFI.userModel.isLogin ||
|
||||
model.currentTab != PeerTabIndex.ab.index ||
|
||||
gFFI.abModel.tags.isEmpty,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
editAbTagDialog(List.empty(), (selectedTags) async {
|
||||
final peers = model.selectedPeers;
|
||||
gFFI.abModel.changeTagForPeers(
|
||||
peers.map((p) => p.id).toList(), selectedTags);
|
||||
gFFI.abModel.pushAb();
|
||||
gFFI.peerTabModel.closeSelection();
|
||||
});
|
||||
},
|
||||
child: Tooltip(
|
||||
message: translate('Edit Tag'), child: Icon(Icons.tag)))
|
||||
.marginOnly(left: isMobile ? 15 : 10),
|
||||
);
|
||||
}
|
||||
|
||||
Widget selectionCount(int count) {
|
||||
return Align(
|
||||
alignment: Alignment.center,
|
||||
child: Text('$count selected'),
|
||||
);
|
||||
}
|
||||
|
||||
Widget selectAll() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
return Offstage(
|
||||
offstage:
|
||||
model.selectedPeers.length >= model.currentTabCachedPeers.length,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
model.selectAll();
|
||||
},
|
||||
child: Tooltip(
|
||||
message: translate('Select All'), child: Icon(Icons.select_all))
|
||||
.marginOnly(left: 10),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget closeSelection() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
model.closeSelection();
|
||||
},
|
||||
child:
|
||||
Tooltip(message: translate('Close'), child: Icon(Icons.clear)))
|
||||
.marginOnly(left: 10);
|
||||
}
|
||||
}
|
||||
|
||||
class PeerSearchBar extends StatefulWidget {
|
||||
|
@ -172,6 +172,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final peers = snapshot.data!;
|
||||
gFFI.peerTabModel.setCurrentTabCachedPeers(peers);
|
||||
final cards = <Widget>[];
|
||||
for (final peer in peers) {
|
||||
final visibilityChild = VisibilityDetector(
|
||||
|
@ -131,6 +131,12 @@ class AbModel {
|
||||
peers.add(peer);
|
||||
}
|
||||
|
||||
void addPeers(List<Peer> ps) {
|
||||
for (var p in ps) {
|
||||
addPeer(p);
|
||||
}
|
||||
}
|
||||
|
||||
void addTag(String tag) async {
|
||||
if (tagContainBy(tag)) {
|
||||
return;
|
||||
@ -146,6 +152,14 @@ class AbModel {
|
||||
it.first.tags = tags;
|
||||
}
|
||||
|
||||
void changeTagForPeers(List<String> ids, List<dynamic> tags) {
|
||||
peers.map((e) {
|
||||
if (ids.contains(e.id)) {
|
||||
e.tags = tags;
|
||||
}
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Future<void> pushAb() async {
|
||||
debugPrint("pushAb");
|
||||
final api = "${await bind.mainGetApiServer()}/api/ab";
|
||||
@ -192,6 +206,10 @@ class AbModel {
|
||||
peers.removeWhere((element) => element.id == id);
|
||||
}
|
||||
|
||||
void deletePeers(List<String> ids) {
|
||||
peers.removeWhere((e) => ids.contains(e.id));
|
||||
}
|
||||
|
||||
void deleteTag(String tag) {
|
||||
gFFI.abModel.selectedTags.remove(tag);
|
||||
tags.removeWhere((element) => element == tag);
|
||||
|
@ -1,10 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/models/peer_model.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../common.dart';
|
||||
import 'model.dart';
|
||||
|
||||
const int groupTabIndex = 4;
|
||||
enum PeerTabIndex {
|
||||
recent,
|
||||
fav,
|
||||
lan,
|
||||
ab,
|
||||
group,
|
||||
}
|
||||
|
||||
const String defaultGroupTabname = 'Group';
|
||||
|
||||
class PeerTabModel with ChangeNotifier {
|
||||
@ -26,6 +35,11 @@ class PeerTabModel with ChangeNotifier {
|
||||
Icons.group,
|
||||
];
|
||||
List<int> get indexs => List.generate(tabNames.length, (index) => index);
|
||||
List<Peer> _selectedPeers = List.empty(growable: true);
|
||||
List<Peer> get selectedPeers => _selectedPeers;
|
||||
bool get multiSelectionMode => _selectedPeers.isNotEmpty;
|
||||
List<Peer> _currentTabCachedPeers = List.empty(growable: true);
|
||||
List<Peer> get currentTabCachedPeers => _currentTabCachedPeers;
|
||||
|
||||
PeerTabModel(this.parent) {
|
||||
// init currentTab
|
||||
@ -45,7 +59,7 @@ class PeerTabModel with ChangeNotifier {
|
||||
|
||||
String tabTooltip(int index, String groupName) {
|
||||
if (index >= 0 && index < tabNames.length) {
|
||||
if (index == groupTabIndex) {
|
||||
if (index == PeerTabIndex.group.index) {
|
||||
if (gFFI.userModel.isAdmin.value || groupName.isEmpty) {
|
||||
return translate(defaultGroupTabname);
|
||||
} else {
|
||||
@ -66,4 +80,39 @@ class PeerTabModel with ChangeNotifier {
|
||||
assert(false);
|
||||
return Icons.help;
|
||||
}
|
||||
|
||||
togglePeerSelect(Peer peer) {
|
||||
if (_selectedPeers.firstWhereOrNull((p) => p.id == peer.id) != null) {
|
||||
_selectedPeers.removeWhere((p) => p.id == peer.id);
|
||||
} else {
|
||||
_selectedPeers.add(peer);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
onPeerCardTap(Peer peer) {
|
||||
if (!multiSelectionMode) return;
|
||||
togglePeerSelect(peer);
|
||||
}
|
||||
|
||||
closeSelection() {
|
||||
_selectedPeers.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
setCurrentTabCachedPeers(List<Peer> peers) {
|
||||
Future.delayed(Duration.zero, () {
|
||||
_currentTabCachedPeers = peers;
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
selectAll() {
|
||||
_selectedPeers = _currentTabCachedPeers.toList();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool isPeerSelected(String id) {
|
||||
return selectedPeers.firstWhereOrNull((p) => p.id == id) != null;
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ bool refreshingUser = false;
|
||||
class UserModel {
|
||||
final RxString userName = ''.obs;
|
||||
final RxBool isAdmin = false.obs;
|
||||
bool get isLogin => userName.isNotEmpty;
|
||||
WeakReference<FFI> parent;
|
||||
|
||||
UserModel(this.parent);
|
||||
|
@ -978,6 +978,11 @@ impl PeerConfig {
|
||||
config
|
||||
}
|
||||
Err(err) => {
|
||||
if let confy::ConfyError::GeneralLoadError(err) = &err {
|
||||
if err.kind() == std::io::ErrorKind::NotFound {
|
||||
return Default::default();
|
||||
}
|
||||
}
|
||||
log::error!("Failed to load peer config '{}': {}", id, err);
|
||||
Default::default()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user