| 
									
										
										
										
											2023-09-14 16:30:45 +08:00
										 |  |  | import 'dart:ui' as ui; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import 'package:bot_toast/bot_toast.dart'; | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | import 'package:flutter/material.dart'; | 
					
						
							| 
									
										
										
										
											2022-12-04 15:15:48 +08:00
										 |  |  | import 'package:flutter_hbb/common/widgets/address_book.dart'; | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  | import 'package:flutter_hbb/common/widgets/dialog.dart'; | 
					
						
							| 
									
										
										
										
											2022-12-11 21:40:35 +08:00
										 |  |  | import 'package:flutter_hbb/common/widgets/my_group.dart'; | 
					
						
							| 
									
										
										
										
											2022-09-22 15:35:46 +08:00
										 |  |  | import 'package:flutter_hbb/common/widgets/peers_view.dart'; | 
					
						
							|  |  |  | import 'package:flutter_hbb/common/widgets/peer_card.dart'; | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | import 'package:flutter_hbb/consts.dart'; | 
					
						
							| 
									
										
										
										
											2022-12-04 15:15:48 +08:00
										 |  |  | import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; | 
					
						
							| 
									
										
										
										
											2023-09-14 16:30:45 +08:00
										 |  |  | import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart' | 
					
						
							|  |  |  |     as mod_menu; | 
					
						
							|  |  |  | import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; | 
					
						
							| 
									
										
										
										
											2023-08-16 08:59:50 +08:00
										 |  |  | import 'package:flutter_hbb/models/ab_model.dart'; | 
					
						
							| 
									
										
										
										
											2023-06-21 16:04:52 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-03 15:07:45 +08:00
										 |  |  | import 'package:flutter_hbb/models/peer_tab_model.dart'; | 
					
						
							| 
									
										
										
										
											2023-10-02 19:32:28 +08:00
										 |  |  | import 'package:flutter_svg/flutter_svg.dart'; | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | import 'package:get/get.dart'; | 
					
						
							| 
									
										
										
										
											2023-02-03 15:07:45 +08:00
										 |  |  | import 'package:provider/provider.dart'; | 
					
						
							| 
									
										
										
										
											2023-10-02 19:32:28 +08:00
										 |  |  | import 'package:pull_down_button/pull_down_button.dart'; | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import '../../common.dart'; | 
					
						
							|  |  |  | import '../../models/platform_model.dart'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class PeerTabPage extends StatefulWidget { | 
					
						
							| 
									
										
										
										
											2022-12-04 15:15:48 +08:00
										 |  |  |   const PeerTabPage({Key? key}) : super(key: key); | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |   @override | 
					
						
							|  |  |  |   State<PeerTabPage> createState() => _PeerTabPageState(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-04 15:15:48 +08:00
										 |  |  | class _TabEntry { | 
					
						
							|  |  |  |   final Widget widget; | 
					
						
							| 
									
										
										
										
											2023-06-23 15:10:10 +08:00
										 |  |  |   final Function({dynamic hint}) load; | 
					
						
							| 
									
										
										
										
											2022-12-11 21:40:35 +08:00
										 |  |  |   _TabEntry(this.widget, this.load); | 
					
						
							| 
									
										
										
										
											2022-12-04 15:15:48 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-05 09:33:01 +08:00
										 |  |  | EdgeInsets? _menuPadding() { | 
					
						
							|  |  |  |   return isDesktop ? kDesktopMenuPadding : null; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | class _PeerTabPageState extends State<PeerTabPage> | 
					
						
							|  |  |  |     with SingleTickerProviderStateMixin { | 
					
						
							| 
									
										
										
										
											2022-12-04 15:15:48 +08:00
										 |  |  |   final List<_TabEntry> entries = [ | 
					
						
							|  |  |  |     _TabEntry( | 
					
						
							|  |  |  |         RecentPeersView( | 
					
						
							| 
									
										
										
										
											2022-12-05 09:33:01 +08:00
										 |  |  |           menuPadding: _menuPadding(), | 
					
						
							| 
									
										
										
										
											2022-12-04 15:15:48 +08:00
										 |  |  |         ), | 
					
						
							|  |  |  |         bind.mainLoadRecentPeers), | 
					
						
							|  |  |  |     _TabEntry( | 
					
						
							|  |  |  |         FavoritePeersView( | 
					
						
							| 
									
										
										
										
											2022-12-05 09:33:01 +08:00
										 |  |  |           menuPadding: _menuPadding(), | 
					
						
							| 
									
										
										
										
											2022-12-04 15:15:48 +08:00
										 |  |  |         ), | 
					
						
							|  |  |  |         bind.mainLoadFavPeers), | 
					
						
							|  |  |  |     _TabEntry( | 
					
						
							|  |  |  |         DiscoveredPeersView( | 
					
						
							| 
									
										
										
										
											2022-12-05 09:33:01 +08:00
										 |  |  |           menuPadding: _menuPadding(), | 
					
						
							| 
									
										
										
										
											2022-12-04 15:15:48 +08:00
										 |  |  |         ), | 
					
						
							|  |  |  |         bind.mainDiscover), | 
					
						
							|  |  |  |     _TabEntry( | 
					
						
							| 
									
										
										
										
											2022-12-05 09:33:01 +08:00
										 |  |  |         AddressBook( | 
					
						
							|  |  |  |           menuPadding: _menuPadding(), | 
					
						
							| 
									
										
										
										
											2022-12-04 15:15:48 +08:00
										 |  |  |         ), | 
					
						
							| 
									
										
										
										
											2023-06-23 15:10:10 +08:00
										 |  |  |         ({dynamic hint}) => gFFI.abModel.pullAb(force: hint == null)), | 
					
						
							| 
									
										
										
										
											2022-12-11 21:40:35 +08:00
										 |  |  |     _TabEntry( | 
					
						
							| 
									
										
										
										
											2023-06-21 11:51:35 +08:00
										 |  |  |       MyGroup( | 
					
						
							|  |  |  |         menuPadding: _menuPadding(), | 
					
						
							|  |  |  |       ), | 
					
						
							| 
									
										
										
										
											2023-06-23 15:10:10 +08:00
										 |  |  |       ({dynamic hint}) => gFFI.groupModel.pull(force: hint == null), | 
					
						
							| 
									
										
										
										
											2023-06-21 11:51:35 +08:00
										 |  |  |     ), | 
					
						
							| 
									
										
										
										
											2022-12-04 15:15:48 +08:00
										 |  |  |   ]; | 
					
						
							| 
									
										
										
										
											2023-09-14 16:30:45 +08:00
										 |  |  |   RelativeRect? mobileTabContextMenuPos; | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void initState() { | 
					
						
							| 
									
										
										
										
											2023-08-10 22:27:35 +08:00
										 |  |  |     final uiType = bind.getLocalFlutterOption(k: 'peer-card-ui-type'); | 
					
						
							| 
									
										
										
										
											2022-11-18 10:19:55 +08:00
										 |  |  |     if (uiType != '') { | 
					
						
							| 
									
										
										
										
											2023-11-01 00:59:28 +05:30
										 |  |  |       peerCardUiType.value = int.parse(uiType) == 0 | 
					
						
							| 
									
										
										
										
											2023-11-24 16:40:18 +08:00
										 |  |  |           ? PeerUiType.grid | 
					
						
							|  |  |  |           : int.parse(uiType) == 1 | 
					
						
							|  |  |  |               ? PeerUiType.tile | 
					
						
							|  |  |  |               : PeerUiType.list; | 
					
						
							| 
									
										
										
										
											2022-11-18 10:19:55 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-06-29 15:18:35 +08:00
										 |  |  |     hideAbTagsPanel.value = | 
					
						
							|  |  |  |         bind.mainGetLocalOption(key: "hideAbTagsPanel").isNotEmpty; | 
					
						
							| 
									
										
										
										
											2022-12-04 15:15:48 +08:00
										 |  |  |     super.initState(); | 
					
						
							| 
									
										
										
										
											2022-11-10 21:25:12 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-11 21:40:35 +08:00
										 |  |  |   Future<void> handleTabSelection(int tabIndex) async { | 
					
						
							|  |  |  |     if (tabIndex < entries.length) { | 
					
						
							| 
									
										
										
										
											2023-02-03 15:07:45 +08:00
										 |  |  |       gFFI.peerTabModel.setCurrentTab(tabIndex); | 
					
						
							| 
									
										
										
										
											2023-06-23 15:10:10 +08:00
										 |  |  |       entries[tabIndex].load(hint: false); | 
					
						
							| 
									
										
										
										
											2022-12-11 21:40:35 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |     final model = Provider.of<PeerTabModel>(context); | 
					
						
							|  |  |  |     Widget selectionWrap(Widget widget) { | 
					
						
							|  |  |  |       return model.multiSelectionMode ? createMultiSelectionBar() : widget; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |     return Column( | 
					
						
							|  |  |  |       textBaseline: TextBaseline.ideographic, | 
					
						
							|  |  |  |       crossAxisAlignment: CrossAxisAlignment.start, | 
					
						
							|  |  |  |       children: [ | 
					
						
							|  |  |  |         SizedBox( | 
					
						
							| 
									
										
										
										
											2023-06-24 18:27:47 +08:00
										 |  |  |           height: 32, | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |           child: Container( | 
					
						
							| 
									
										
										
										
											2023-03-03 20:53:42 +01:00
										 |  |  |             padding: isDesktop ? null : EdgeInsets.symmetric(horizontal: 2), | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |             child: selectionWrap(Row( | 
					
						
							| 
									
										
										
										
											2023-03-03 20:53:42 +01:00
										 |  |  |               crossAxisAlignment: CrossAxisAlignment.center, | 
					
						
							|  |  |  |               children: [ | 
					
						
							| 
									
										
										
										
											2023-09-14 16:30:45 +08:00
										 |  |  |                 Expanded( | 
					
						
							|  |  |  |                     child: | 
					
						
							|  |  |  |                         visibleContextMenuListener(_createSwitchBar(context))), | 
					
						
							| 
									
										
										
										
											2023-10-02 19:32:28 +08:00
										 |  |  |                 if (isMobile) | 
					
						
							|  |  |  |                   ..._mobileRightActions(context) | 
					
						
							|  |  |  |                 else | 
					
						
							|  |  |  |                   ..._desktopRightActions(context) | 
					
						
							| 
									
										
										
										
											2023-03-03 20:53:42 +01:00
										 |  |  |               ], | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |             )), | 
					
						
							| 
									
										
										
										
											2023-03-03 20:53:42 +01:00
										 |  |  |           ), | 
					
						
							| 
									
										
										
										
											2023-09-25 21:04:40 +08:00
										 |  |  |         ).paddingOnly(right: isDesktop ? 12 : 0), | 
					
						
							| 
									
										
										
										
											2022-09-28 11:20:57 +08:00
										 |  |  |         _createPeersView(), | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |       ], | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 11:20:57 +08:00
										 |  |  |   Widget _createSwitchBar(BuildContext context) { | 
					
						
							| 
									
										
										
										
											2023-06-21 16:04:52 +08:00
										 |  |  |     final model = Provider.of<PeerTabModel>(context); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ListView( | 
					
						
							| 
									
										
										
										
											2023-02-03 15:07:45 +08:00
										 |  |  |         scrollDirection: Axis.horizontal, | 
					
						
							|  |  |  |         physics: NeverScrollableScrollPhysics(), | 
					
						
							| 
									
										
										
										
											2023-09-14 16:30:45 +08:00
										 |  |  |         children: model.visibleIndexs.map((t) { | 
					
						
							| 
									
										
										
										
											2023-06-24 18:27:47 +08:00
										 |  |  |           final selected = model.currentTab == t; | 
					
						
							|  |  |  |           final color = selected | 
					
						
							|  |  |  |               ? MyTheme.tabbar(context).selectedTextColor | 
					
						
							|  |  |  |               : MyTheme.tabbar(context).unSelectedTextColor | 
					
						
							|  |  |  |             ?..withOpacity(0.5); | 
					
						
							|  |  |  |           final hover = false.obs; | 
					
						
							|  |  |  |           final deco = BoxDecoration( | 
					
						
							|  |  |  |               color: Theme.of(context).colorScheme.background, | 
					
						
							|  |  |  |               borderRadius: BorderRadius.circular(6)); | 
					
						
							|  |  |  |           final decoBorder = BoxDecoration( | 
					
						
							|  |  |  |               border: Border( | 
					
						
							|  |  |  |             bottom: BorderSide(width: 2, color: color!), | 
					
						
							|  |  |  |           )); | 
					
						
							|  |  |  |           return Obx(() => InkWell( | 
					
						
							|  |  |  |                 child: Container( | 
					
						
							| 
									
										
										
										
											2023-09-14 10:17:03 +08:00
										 |  |  |                   decoration: (hover.value | 
					
						
							|  |  |  |                       ? (selected ? decoBorder : deco) | 
					
						
							|  |  |  |                       : (selected ? decoBorder : null)), | 
					
						
							| 
									
										
										
										
											2023-06-24 18:27:47 +08:00
										 |  |  |                   child: Tooltip( | 
					
						
							| 
									
										
										
										
											2023-09-14 16:30:45 +08:00
										 |  |  |                     preferBelow: false, | 
					
						
							| 
									
										
										
										
											2023-09-14 10:17:03 +08:00
										 |  |  |                     message: model.tabTooltip(t), | 
					
						
							| 
									
										
										
										
											2023-09-14 16:30:45 +08:00
										 |  |  |                     onTriggered: isMobile ? mobileShowTabVisibilityMenu : null, | 
					
						
							| 
									
										
										
										
											2023-06-24 18:27:47 +08:00
										 |  |  |                     child: Icon(model.tabIcon(t), color: color), | 
					
						
							|  |  |  |                   ).paddingSymmetric(horizontal: 4), | 
					
						
							|  |  |  |                 ).paddingSymmetric(horizontal: 4), | 
					
						
							|  |  |  |                 onTap: () async { | 
					
						
							|  |  |  |                   await handleTabSelection(t); | 
					
						
							| 
									
										
										
										
											2023-08-10 22:27:35 +08:00
										 |  |  |                   await bind.setLocalFlutterOption( | 
					
						
							| 
									
										
										
										
											2023-06-24 18:27:47 +08:00
										 |  |  |                       k: 'peer-tab-index', v: t.toString()); | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |                 onHover: (value) => hover.value = value, | 
					
						
							|  |  |  |               )); | 
					
						
							| 
									
										
										
										
											2023-02-03 15:07:45 +08:00
										 |  |  |         }).toList()); | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 11:20:57 +08:00
										 |  |  |   Widget _createPeersView() { | 
					
						
							| 
									
										
										
										
											2023-02-03 15:07:45 +08:00
										 |  |  |     final model = Provider.of<PeerTabModel>(context); | 
					
						
							|  |  |  |     Widget child; | 
					
						
							| 
									
										
										
										
											2023-09-14 16:30:45 +08:00
										 |  |  |     if (model.visibleIndexs.isEmpty) { | 
					
						
							|  |  |  |       child = visibleContextMenuListener(Row( | 
					
						
							|  |  |  |         children: [Expanded(child: InkWell())], | 
					
						
							|  |  |  |       )); | 
					
						
							| 
									
										
										
										
											2023-02-03 15:07:45 +08:00
										 |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2023-09-14 16:30:45 +08:00
										 |  |  |       if (model.visibleIndexs.contains(model.currentTab)) { | 
					
						
							| 
									
										
										
										
											2023-02-03 15:07:45 +08:00
										 |  |  |         child = entries[model.currentTab].widget; | 
					
						
							| 
									
										
										
										
											2022-12-11 21:40:35 +08:00
										 |  |  |       } else { | 
					
						
							| 
									
										
										
										
											2023-09-14 16:30:45 +08:00
										 |  |  |         debugPrint("should not happen! currentTab not in visibleIndexs"); | 
					
						
							| 
									
										
										
										
											2023-06-17 20:48:58 +08:00
										 |  |  |         Future.delayed(Duration.zero, () { | 
					
						
							| 
									
										
										
										
											2023-06-21 16:04:52 +08:00
										 |  |  |           model.setCurrentTab(model.indexs[0]); | 
					
						
							| 
									
										
										
										
											2023-06-17 20:48:58 +08:00
										 |  |  |         }); | 
					
						
							| 
									
										
										
										
											2023-02-03 15:07:45 +08:00
										 |  |  |         child = entries[0].widget; | 
					
						
							| 
									
										
										
										
											2022-12-11 21:40:35 +08:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-02-03 15:07:45 +08:00
										 |  |  |     } | 
					
						
							|  |  |  |     return Expanded( | 
					
						
							|  |  |  |         child: child.marginSymmetric(vertical: isDesktop ? 12.0 : 6.0)); | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-14 10:17:03 +08:00
										 |  |  |   Widget _createRefresh( | 
					
						
							|  |  |  |       {required PeerTabIndex index, required RxBool loading}) { | 
					
						
							|  |  |  |     final model = Provider.of<PeerTabModel>(context); | 
					
						
							| 
									
										
										
										
											2023-06-21 11:51:35 +08:00
										 |  |  |     final textColor = Theme.of(context).textTheme.titleLarge?.color; | 
					
						
							| 
									
										
										
										
											2023-08-19 18:12:03 +08:00
										 |  |  |     return Offstage( | 
					
						
							| 
									
										
										
										
											2023-09-14 10:17:03 +08:00
										 |  |  |       offstage: model.currentTab != index.index, | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |       child: RefreshWidget( | 
					
						
							| 
									
										
										
										
											2023-08-18 16:13:24 +08:00
										 |  |  |           onPressed: () { | 
					
						
							|  |  |  |             if (gFFI.peerTabModel.currentTab < entries.length) { | 
					
						
							|  |  |  |               entries[gFFI.peerTabModel.currentTab].load(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           }, | 
					
						
							| 
									
										
										
										
											2023-09-14 10:17:03 +08:00
										 |  |  |           spinning: loading, | 
					
						
							| 
									
										
										
										
											2023-08-18 16:13:24 +08:00
										 |  |  |           child: RotatedBox( | 
					
						
							|  |  |  |               quarterTurns: 2, | 
					
						
							|  |  |  |               child: Tooltip( | 
					
						
							|  |  |  |                   message: translate('Refresh'), | 
					
						
							|  |  |  |                   child: Icon( | 
					
						
							|  |  |  |                     Icons.refresh, | 
					
						
							|  |  |  |                     size: 18, | 
					
						
							|  |  |  |                     color: textColor, | 
					
						
							|  |  |  |                   )))), | 
					
						
							| 
									
										
										
										
											2023-06-21 11:51:35 +08:00
										 |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |   Widget _createPeerViewTypeSwitch(BuildContext context) { | 
					
						
							| 
									
										
										
										
											2023-10-26 04:02:34 +05:30
										 |  |  |     return PeerViewDropdown(); | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-09 22:00:15 +08:00
										 |  |  |   Widget _createMultiSelection() { | 
					
						
							|  |  |  |     final textColor = Theme.of(context).textTheme.titleLarge?.color; | 
					
						
							|  |  |  |     final model = Provider.of<PeerTabModel>(context); | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |     return _hoverAction( | 
					
						
							|  |  |  |       context: context, | 
					
						
							|  |  |  |       onTap: () { | 
					
						
							|  |  |  |         model.setMultiSelectionMode(true); | 
					
						
							| 
									
										
										
										
											2023-10-02 19:32:28 +08:00
										 |  |  |         if (isMobile && Navigator.canPop(context)) { | 
					
						
							|  |  |  |           Navigator.pop(context); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |       }, | 
					
						
							|  |  |  |       child: Tooltip( | 
					
						
							|  |  |  |           message: translate('Select'), | 
					
						
							| 
									
										
										
										
											2023-10-02 19:32:28 +08:00
										 |  |  |           child: SvgPicture.asset( | 
					
						
							|  |  |  |             "assets/checkbox-outline.svg", | 
					
						
							|  |  |  |             width: 18, | 
					
						
							|  |  |  |             height: 18, | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |             color: textColor, | 
					
						
							|  |  |  |           )), | 
					
						
							| 
									
										
										
										
											2023-08-09 22:00:15 +08:00
										 |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-14 16:30:45 +08:00
										 |  |  |   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); | 
					
						
							|  |  |  |                   } | 
					
						
							|  |  |  |                 }), | 
					
						
							| 
									
										
										
										
											2023-09-14 10:17:03 +08:00
										 |  |  |             Expanded(child: Text(model.tabTooltip(i))), | 
					
						
							| 
									
										
										
										
											2023-09-14 16:30:45 +08:00
										 |  |  |           ], | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |       )); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     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, | 
					
						
							| 
									
										
										
										
											2023-09-14 10:17:03 +08:00
										 |  |  |           text: model.tabTooltip(i), | 
					
						
							| 
									
										
										
										
											2023-09-14 16:30:45 +08:00
										 |  |  |           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()); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |   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); | 
					
						
							| 
									
										
										
										
											2023-09-14 10:17:03 +08:00
										 |  |  |     if (model.currentTab == PeerTabIndex.group.index) { | 
					
						
							|  |  |  |       return Offstage(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |     return _hoverAction( | 
					
						
							|  |  |  |         context: context, | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |         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: | 
					
						
							| 
									
										
										
										
											2023-08-16 08:59:50 +08:00
										 |  |  |                 { | 
					
						
							|  |  |  |                   bool hasSynced = false; | 
					
						
							|  |  |  |                   if (shouldSyncAb()) { | 
					
						
							|  |  |  |                     for (var p in peers) { | 
					
						
							|  |  |  |                       if (await bind.mainPeerExists(id: p.id)) { | 
					
						
							|  |  |  |                         hasSynced = true; | 
					
						
							|  |  |  |                       } | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                   } | 
					
						
							|  |  |  |                   gFFI.abModel.deletePeers(peers.map((p) => p.id).toList()); | 
					
						
							| 
									
										
										
										
											2023-08-16 10:18:29 +08:00
										 |  |  |                   final future = gFFI.abModel.pushAb(); | 
					
						
							| 
									
										
										
										
											2023-08-16 08:59:50 +08:00
										 |  |  |                   if (hasSynced) { | 
					
						
							| 
									
										
										
										
											2023-08-17 18:21:37 +08:00
										 |  |  |                     gFFI.abModel.reSyncToast(future); | 
					
						
							| 
									
										
										
										
											2023-08-16 08:59:50 +08:00
										 |  |  |                   } | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |                 break; | 
					
						
							|  |  |  |               default: | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-08-09 22:00:15 +08:00
										 |  |  |             gFFI.peerTabModel.setMultiSelectionMode(false); | 
					
						
							| 
									
										
										
										
											2023-08-16 10:18:29 +08:00
										 |  |  |             if (model.currentTab != 3) showToast(translate('Successful')); | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-08 18:06:31 +08:00
										 |  |  |           deletePeerConfirmDialog(onSubmit, translate('Delete')); | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |         }, | 
					
						
							|  |  |  |         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
 | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |       child: _hoverAction( | 
					
						
							|  |  |  |         context: context, | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |         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); | 
					
						
							| 
									
										
										
										
											2023-08-09 22:00:15 +08:00
										 |  |  |           model.setMultiSelectionMode(false); | 
					
						
							| 
									
										
										
										
											2023-08-04 15:32:09 +08:00
										 |  |  |           showToast(translate('Successful')); | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |         }, | 
					
						
							|  |  |  |         child: Tooltip( | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |             message: translate('Add to Favorites'), | 
					
						
							|  |  |  |             child: Icon(model.icons[PeerTabIndex.fav.index])), | 
					
						
							|  |  |  |       ).marginOnly(left: isMobile ? 11 : 6), | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Widget addSelectionToAb() { | 
					
						
							|  |  |  |     final model = Provider.of<PeerTabModel>(context); | 
					
						
							|  |  |  |     return Offstage( | 
					
						
							|  |  |  |       offstage: | 
					
						
							|  |  |  |           !gFFI.userModel.isLogin || model.currentTab == PeerTabIndex.ab.index, | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |       child: _hoverAction( | 
					
						
							|  |  |  |         context: context, | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |         onTap: () { | 
					
						
							| 
									
										
										
										
											2023-08-16 13:30:38 +08:00
										 |  |  |           if (gFFI.abModel.isFull(true)) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |           final peers = model.selectedPeers; | 
					
						
							|  |  |  |           gFFI.abModel.addPeers(peers); | 
					
						
							| 
									
										
										
										
											2023-08-16 13:30:38 +08:00
										 |  |  |           final future = gFFI.abModel.pushAb(); | 
					
						
							| 
									
										
										
										
											2023-08-09 22:00:15 +08:00
										 |  |  |           model.setMultiSelectionMode(false); | 
					
						
							| 
									
										
										
										
											2023-08-16 13:30:38 +08:00
										 |  |  |           Future.delayed(Duration.zero, () async { | 
					
						
							|  |  |  |             await future; | 
					
						
							|  |  |  |             await Future.delayed(Duration(seconds: 2)); // toast
 | 
					
						
							|  |  |  |             gFFI.abModel.isFull(true); | 
					
						
							|  |  |  |           }); | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |         }, | 
					
						
							|  |  |  |         child: Tooltip( | 
					
						
							| 
									
										
										
										
											2023-11-06 20:12:01 +08:00
										 |  |  |             message: translate('Add to address book'), | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |             child: Icon(model.icons[PeerTabIndex.ab.index])), | 
					
						
							|  |  |  |       ).marginOnly(left: isMobile ? 11 : 6), | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Widget editSelectionTags() { | 
					
						
							|  |  |  |     final model = Provider.of<PeerTabModel>(context); | 
					
						
							|  |  |  |     return Offstage( | 
					
						
							|  |  |  |       offstage: !gFFI.userModel.isLogin || | 
					
						
							|  |  |  |           model.currentTab != PeerTabIndex.ab.index || | 
					
						
							|  |  |  |           gFFI.abModel.tags.isEmpty, | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |       child: _hoverAction( | 
					
						
							|  |  |  |               context: context, | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |               onTap: () { | 
					
						
							|  |  |  |                 editAbTagDialog(List.empty(), (selectedTags) async { | 
					
						
							|  |  |  |                   final peers = model.selectedPeers; | 
					
						
							|  |  |  |                   gFFI.abModel.changeTagForPeers( | 
					
						
							|  |  |  |                       peers.map((p) => p.id).toList(), selectedTags); | 
					
						
							|  |  |  |                   gFFI.abModel.pushAb(); | 
					
						
							| 
									
										
										
										
											2023-08-09 22:00:15 +08:00
										 |  |  |                   model.setMultiSelectionMode(false); | 
					
						
							| 
									
										
										
										
											2023-08-04 15:32:09 +08:00
										 |  |  |                   showToast(translate('Successful')); | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |                 }); | 
					
						
							|  |  |  |               }, | 
					
						
							|  |  |  |               child: Tooltip( | 
					
						
							|  |  |  |                   message: translate('Edit Tag'), child: Icon(Icons.tag))) | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |           .marginOnly(left: isMobile ? 11 : 6), | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Widget selectionCount(int count) { | 
					
						
							|  |  |  |     return Align( | 
					
						
							|  |  |  |       alignment: Alignment.center, | 
					
						
							| 
									
										
										
										
											2023-08-21 22:42:16 +02:00
										 |  |  |       child: Text('$count ${translate('Selected')}'), | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Widget selectAll() { | 
					
						
							|  |  |  |     final model = Provider.of<PeerTabModel>(context); | 
					
						
							|  |  |  |     return Offstage( | 
					
						
							|  |  |  |       offstage: | 
					
						
							|  |  |  |           model.selectedPeers.length >= model.currentTabCachedPeers.length, | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |       child: _hoverAction( | 
					
						
							|  |  |  |         context: context, | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |         onTap: () { | 
					
						
							|  |  |  |           model.selectAll(); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         child: Tooltip( | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |             message: translate('Select All'), child: Icon(Icons.select_all)), | 
					
						
							|  |  |  |       ).marginOnly(left: 6), | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Widget closeSelection() { | 
					
						
							|  |  |  |     final model = Provider.of<PeerTabModel>(context); | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |     return _hoverAction( | 
					
						
							|  |  |  |             context: context, | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |             onTap: () { | 
					
						
							| 
									
										
										
										
											2023-08-09 22:00:15 +08:00
										 |  |  |               model.setMultiSelectionMode(false); | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |             }, | 
					
						
							|  |  |  |             child: | 
					
						
							|  |  |  |                 Tooltip(message: translate('Close'), child: Icon(Icons.clear))) | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |         .marginOnly(left: 6); | 
					
						
							| 
									
										
										
										
											2023-08-03 16:48:14 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-10-02 19:32:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   Widget _toggleTags() { | 
					
						
							|  |  |  |     return _hoverAction( | 
					
						
							|  |  |  |         context: context, | 
					
						
							|  |  |  |         hoverableWhenfalse: hideAbTagsPanel, | 
					
						
							|  |  |  |         child: Tooltip( | 
					
						
							|  |  |  |             message: translate('Toggle Tags'), | 
					
						
							|  |  |  |             child: Icon( | 
					
						
							|  |  |  |               Icons.tag_rounded, | 
					
						
							|  |  |  |               size: 18, | 
					
						
							|  |  |  |             )), | 
					
						
							|  |  |  |         onTap: () async { | 
					
						
							|  |  |  |           await bind.mainSetLocalOption( | 
					
						
							|  |  |  |               key: "hideAbTagsPanel", value: hideAbTagsPanel.value ? "" : "Y"); | 
					
						
							|  |  |  |           hideAbTagsPanel.value = !hideAbTagsPanel.value; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   List<Widget> _desktopRightActions(BuildContext context) { | 
					
						
							|  |  |  |     final model = Provider.of<PeerTabModel>(context); | 
					
						
							|  |  |  |     return [ | 
					
						
							|  |  |  |       const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13), | 
					
						
							|  |  |  |       _createRefresh(index: PeerTabIndex.ab, loading: gFFI.abModel.abLoading), | 
					
						
							|  |  |  |       _createRefresh( | 
					
						
							|  |  |  |           index: PeerTabIndex.group, loading: gFFI.groupModel.groupLoading), | 
					
						
							|  |  |  |       Offstage( | 
					
						
							|  |  |  |         offstage: model.currentTabCachedPeers.isEmpty, | 
					
						
							|  |  |  |         child: _createMultiSelection(), | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |       _createPeerViewTypeSwitch(context), | 
					
						
							|  |  |  |       Offstage( | 
					
						
							|  |  |  |         offstage: model.currentTab == PeerTabIndex.recent.index, | 
					
						
							|  |  |  |         child: PeerSortDropdown(), | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |       Offstage( | 
					
						
							|  |  |  |         offstage: model.currentTab != PeerTabIndex.ab.index, | 
					
						
							|  |  |  |         child: _toggleTags(), | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   List<Widget> _mobileRightActions(BuildContext context) { | 
					
						
							|  |  |  |     final model = Provider.of<PeerTabModel>(context); | 
					
						
							|  |  |  |     final screenWidth = MediaQuery.of(context).size.width; | 
					
						
							|  |  |  |     final leftIconSize = Theme.of(context).iconTheme.size ?? 24; | 
					
						
							|  |  |  |     final leftActionsSize = | 
					
						
							|  |  |  |         (leftIconSize + (4 + 4) * 2) * model.visibleIndexs.length; | 
					
						
							|  |  |  |     final availableWidth = screenWidth - 10 * 2 - leftActionsSize - 2 * 2; | 
					
						
							|  |  |  |     final searchWidth = 120; | 
					
						
							|  |  |  |     final otherActionWidth = 18 + 10; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     dropDown(List<Widget> menus) { | 
					
						
							|  |  |  |       final padding = 6.0; | 
					
						
							|  |  |  |       final textColor = Theme.of(context).textTheme.titleLarge?.color; | 
					
						
							|  |  |  |       return PullDownButton( | 
					
						
							|  |  |  |         buttonBuilder: | 
					
						
							|  |  |  |             (BuildContext context, Future<void> Function() showMenu) { | 
					
						
							|  |  |  |           return _hoverAction( | 
					
						
							|  |  |  |             context: context, | 
					
						
							|  |  |  |             child: Tooltip( | 
					
						
							|  |  |  |                 message: translate('More'), | 
					
						
							|  |  |  |                 child: SvgPicture.asset( | 
					
						
							|  |  |  |                   "assets/chevron_up_chevron_down.svg", | 
					
						
							|  |  |  |                   width: 18, | 
					
						
							|  |  |  |                   height: 18, | 
					
						
							|  |  |  |                   color: textColor, | 
					
						
							|  |  |  |                 )), | 
					
						
							|  |  |  |             onTap: showMenu, | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         routeTheme: PullDownMenuRouteTheme( | 
					
						
							|  |  |  |             width: menus.length * (otherActionWidth + padding * 2) * 1.0), | 
					
						
							|  |  |  |         itemBuilder: (context) => [ | 
					
						
							|  |  |  |           PullDownMenuEntryImpl( | 
					
						
							|  |  |  |             child: Row( | 
					
						
							|  |  |  |               mainAxisSize: MainAxisSize.min, | 
					
						
							|  |  |  |               children: menus | 
					
						
							|  |  |  |                   .map((e) => | 
					
						
							|  |  |  |                       Material(child: e.paddingSymmetric(horizontal: padding))) | 
					
						
							|  |  |  |                   .toList(), | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |           ) | 
					
						
							|  |  |  |         ], | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Always show search, refresh
 | 
					
						
							|  |  |  |     List<Widget> actions = [ | 
					
						
							|  |  |  |       const PeerSearchBar(), | 
					
						
							|  |  |  |       if (model.currentTab == PeerTabIndex.ab.index) | 
					
						
							|  |  |  |         _createRefresh(index: PeerTabIndex.ab, loading: gFFI.abModel.abLoading), | 
					
						
							|  |  |  |       if (model.currentTab == PeerTabIndex.group.index) | 
					
						
							|  |  |  |         _createRefresh( | 
					
						
							|  |  |  |             index: PeerTabIndex.group, loading: gFFI.groupModel.groupLoading), | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  |     final List<Widget> dynamicActions = [ | 
					
						
							|  |  |  |       if (model.currentTabCachedPeers.isNotEmpty) _createMultiSelection(), | 
					
						
							|  |  |  |       if (model.currentTab != PeerTabIndex.recent.index) PeerSortDropdown(), | 
					
						
							|  |  |  |       if (model.currentTab == PeerTabIndex.ab.index) _toggleTags() | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  |     final rightWidth = availableWidth - | 
					
						
							|  |  |  |         searchWidth - | 
					
						
							|  |  |  |         (actions.length == 2 ? otherActionWidth : 0); | 
					
						
							|  |  |  |     final availablePositions = rightWidth ~/ otherActionWidth; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (availablePositions < dynamicActions.length && | 
					
						
							|  |  |  |         dynamicActions.length > 1) { | 
					
						
							|  |  |  |       if (availablePositions < 2) { | 
					
						
							|  |  |  |         actions.addAll([ | 
					
						
							|  |  |  |           dropDown(dynamicActions), | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         actions.addAll([ | 
					
						
							|  |  |  |           ...dynamicActions.sublist(0, availablePositions - 1), | 
					
						
							|  |  |  |           dropDown(dynamicActions.sublist(availablePositions - 1)), | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       actions.addAll(dynamicActions); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return actions; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class PeerSearchBar extends StatefulWidget { | 
					
						
							|  |  |  |   const PeerSearchBar({Key? key}) : super(key: key); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   State<StatefulWidget> createState() => _PeerSearchBarState(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _PeerSearchBarState extends State<PeerSearchBar> { | 
					
						
							|  |  |  |   var drawer = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |     return drawer | 
					
						
							|  |  |  |         ? _buildSearchBar() | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |         : _hoverAction( | 
					
						
							|  |  |  |             context: context, | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |             padding: const EdgeInsets.only(right: 2), | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |             onTap: () { | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |               setState(() { | 
					
						
							|  |  |  |                 drawer = true; | 
					
						
							|  |  |  |               }); | 
					
						
							|  |  |  |             }, | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |             child: Tooltip( | 
					
						
							| 
									
										
										
										
											2023-08-12 12:51:30 +05:30
										 |  |  |                 message: translate('Search'), | 
					
						
							|  |  |  |                 child: Icon( | 
					
						
							|  |  |  |                   Icons.search_rounded, | 
					
						
							|  |  |  |                   color: Theme.of(context).hintColor, | 
					
						
							| 
									
										
										
										
											2023-08-16 08:59:50 +08:00
										 |  |  |                 ))); | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Widget _buildSearchBar() { | 
					
						
							|  |  |  |     RxBool focused = false.obs; | 
					
						
							|  |  |  |     FocusNode focusNode = FocusNode(); | 
					
						
							| 
									
										
										
										
											2023-01-30 18:30:38 +08:00
										 |  |  |     focusNode.addListener(() { | 
					
						
							|  |  |  |       focused.value = focusNode.hasFocus; | 
					
						
							| 
									
										
										
										
											2023-02-02 16:45:29 +08:00
										 |  |  |       peerSearchTextController.selection = TextSelection( | 
					
						
							|  |  |  |           baseOffset: 0, | 
					
						
							|  |  |  |           extentOffset: peerSearchTextController.value.text.length); | 
					
						
							| 
									
										
										
										
											2023-01-30 18:30:38 +08:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |     return Container( | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |       width: isMobile ? 120 : 140, | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |       decoration: BoxDecoration( | 
					
						
							| 
									
										
										
										
											2023-02-23 16:49:31 +01:00
										 |  |  |         color: Theme.of(context).colorScheme.background, | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |         borderRadius: BorderRadius.circular(6), | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |       child: Obx(() => Row( | 
					
						
							|  |  |  |             children: [ | 
					
						
							|  |  |  |               Expanded( | 
					
						
							|  |  |  |                 child: Row( | 
					
						
							|  |  |  |                   children: [ | 
					
						
							|  |  |  |                     Icon( | 
					
						
							|  |  |  |                       Icons.search_rounded, | 
					
						
							| 
									
										
										
										
											2022-09-23 16:31:50 +08:00
										 |  |  |                       color: Theme.of(context).hintColor, | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |                     ).marginSymmetric(horizontal: 4), | 
					
						
							|  |  |  |                     Expanded( | 
					
						
							|  |  |  |                       child: TextField( | 
					
						
							|  |  |  |                         autofocus: true, | 
					
						
							|  |  |  |                         controller: peerSearchTextController, | 
					
						
							|  |  |  |                         onChanged: (searchText) { | 
					
						
							|  |  |  |                           peerSearchText.value = searchText; | 
					
						
							|  |  |  |                         }, | 
					
						
							|  |  |  |                         focusNode: focusNode, | 
					
						
							|  |  |  |                         textAlign: TextAlign.start, | 
					
						
							|  |  |  |                         maxLines: 1, | 
					
						
							| 
									
										
										
										
											2022-09-23 16:31:50 +08:00
										 |  |  |                         cursorColor: Theme.of(context) | 
					
						
							|  |  |  |                             .textTheme | 
					
						
							|  |  |  |                             .titleLarge | 
					
						
							|  |  |  |                             ?.color | 
					
						
							|  |  |  |                             ?.withOpacity(0.5), | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |                         cursorHeight: 18, | 
					
						
							|  |  |  |                         cursorWidth: 1, | 
					
						
							|  |  |  |                         style: const TextStyle(fontSize: 14), | 
					
						
							|  |  |  |                         decoration: InputDecoration( | 
					
						
							|  |  |  |                           contentPadding: | 
					
						
							|  |  |  |                               const EdgeInsets.symmetric(vertical: 6), | 
					
						
							|  |  |  |                           hintText: | 
					
						
							|  |  |  |                               focused.value ? null : translate("Search ID"), | 
					
						
							|  |  |  |                           hintStyle: TextStyle( | 
					
						
							| 
									
										
										
										
											2022-09-23 16:31:50 +08:00
										 |  |  |                               fontSize: 14, color: Theme.of(context).hintColor), | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |                           border: InputBorder.none, | 
					
						
							|  |  |  |                           isDense: true, | 
					
						
							|  |  |  |                         ), | 
					
						
							|  |  |  |                       ), | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                     // Icon(Icons.close),
 | 
					
						
							|  |  |  |                     IconButton( | 
					
						
							| 
									
										
										
										
											2023-08-16 08:59:50 +08:00
										 |  |  |                       alignment: Alignment.centerRight, | 
					
						
							|  |  |  |                       padding: const EdgeInsets.only(right: 2), | 
					
						
							|  |  |  |                       onPressed: () { | 
					
						
							|  |  |  |                         setState(() { | 
					
						
							|  |  |  |                           peerSearchTextController.clear(); | 
					
						
							|  |  |  |                           peerSearchText.value = ""; | 
					
						
							|  |  |  |                           drawer = false; | 
					
						
							|  |  |  |                         }); | 
					
						
							|  |  |  |                       }, | 
					
						
							|  |  |  |                       icon: Tooltip( | 
					
						
							|  |  |  |                           message: translate('Close'), | 
					
						
							|  |  |  |                           child: Icon( | 
					
						
							|  |  |  |                             Icons.close, | 
					
						
							|  |  |  |                             color: Theme.of(context).hintColor, | 
					
						
							|  |  |  |                           )), | 
					
						
							| 
									
										
										
										
											2023-08-12 12:51:30 +05:30
										 |  |  |                     ), | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |                   ], | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |               ) | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |           )), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-03-03 20:53:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-26 04:02:34 +05:30
										 |  |  | class PeerViewDropdown extends StatefulWidget { | 
					
						
							|  |  |  |   const PeerViewDropdown({super.key}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   State<PeerViewDropdown> createState() => _PeerViewDropdownState(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _PeerViewDropdownState extends State<PeerViewDropdown> { | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							| 
									
										
										
										
											2023-11-24 16:40:18 +08:00
										 |  |  |     final List<PeerUiType> types = [ | 
					
						
							|  |  |  |       PeerUiType.grid, | 
					
						
							|  |  |  |       PeerUiType.tile, | 
					
						
							|  |  |  |       PeerUiType.list | 
					
						
							|  |  |  |     ]; | 
					
						
							| 
									
										
										
										
											2023-10-26 04:02:34 +05:30
										 |  |  |     final style = TextStyle( | 
					
						
							|  |  |  |         color: Theme.of(context).textTheme.titleLarge?.color, | 
					
						
							|  |  |  |         fontSize: MenuConfig.fontSize, | 
					
						
							|  |  |  |         fontWeight: FontWeight.normal); | 
					
						
							|  |  |  |     List<PopupMenuEntry> items = List.empty(growable: true); | 
					
						
							|  |  |  |     items.add(PopupMenuItem( | 
					
						
							|  |  |  |         height: 36, | 
					
						
							|  |  |  |         enabled: false, | 
					
						
							| 
									
										
										
										
											2023-10-26 13:09:19 +08:00
										 |  |  |         child: Text(translate("Change view"), style: style))); | 
					
						
							| 
									
										
										
										
											2023-11-24 16:40:18 +08:00
										 |  |  |     for (var e in PeerUiType.values) { | 
					
						
							| 
									
										
										
										
											2023-10-26 04:02:34 +05:30
										 |  |  |       items.add(PopupMenuItem( | 
					
						
							|  |  |  |           height: 36, | 
					
						
							|  |  |  |           child: Obx(() => Center( | 
					
						
							|  |  |  |                 child: SizedBox( | 
					
						
							|  |  |  |                   height: 36, | 
					
						
							|  |  |  |                   child: getRadio<PeerUiType>( | 
					
						
							| 
									
										
										
										
											2023-11-24 16:40:18 +08:00
										 |  |  |                       Text( | 
					
						
							|  |  |  |                           translate(types.indexOf(e) == 0 | 
					
						
							|  |  |  |                               ? 'Big tiles' | 
					
						
							|  |  |  |                               : types.indexOf(e) == 1 | 
					
						
							|  |  |  |                                   ? 'Small tiles' | 
					
						
							|  |  |  |                                   : 'List'), | 
					
						
							|  |  |  |                           style: style), | 
					
						
							|  |  |  |                       e, | 
					
						
							| 
									
										
										
										
											2023-10-26 04:02:34 +05:30
										 |  |  |                       peerCardUiType.value, | 
					
						
							| 
									
										
										
										
											2023-11-24 16:40:18 +08:00
										 |  |  |                       dense: true, (PeerUiType? v) async { | 
					
						
							| 
									
										
										
										
											2023-10-26 04:02:34 +05:30
										 |  |  |                     if (v != null) { | 
					
						
							|  |  |  |                       peerCardUiType.value = v; | 
					
						
							| 
									
										
										
										
											2023-10-26 04:46:24 +05:30
										 |  |  |                       setState(() {}); | 
					
						
							| 
									
										
										
										
											2023-10-26 04:02:34 +05:30
										 |  |  |                       await bind.setLocalFlutterOption( | 
					
						
							|  |  |  |                         k: "peer-card-ui-type", | 
					
						
							|  |  |  |                         v: peerCardUiType.value.index.toString(), | 
					
						
							|  |  |  |                       ); | 
					
						
							| 
									
										
										
										
											2023-11-24 16:40:18 +08:00
										 |  |  |                     } | 
					
						
							|  |  |  |                   }), | 
					
						
							| 
									
										
										
										
											2023-10-26 04:02:34 +05:30
										 |  |  |                 ), | 
					
						
							|  |  |  |               )))); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-04 14:50:04 +05:30
										 |  |  |     var menuPos = RelativeRect.fromLTRB(0, 0, 0, 0); | 
					
						
							| 
									
										
										
										
											2023-10-26 04:02:34 +05:30
										 |  |  |     return _hoverAction( | 
					
						
							| 
									
										
										
										
											2023-11-24 16:40:18 +08:00
										 |  |  |         context: context, | 
					
						
							|  |  |  |         child: Tooltip( | 
					
						
							|  |  |  |             message: translate('Change view'), | 
					
						
							|  |  |  |             child: Icon( | 
					
						
							| 
									
										
										
										
											2023-10-26 04:02:34 +05:30
										 |  |  |               peerCardUiType.value == PeerUiType.grid | 
					
						
							|  |  |  |                   ? Icons.grid_view_rounded | 
					
						
							|  |  |  |                   : peerCardUiType.value == PeerUiType.tile | 
					
						
							|  |  |  |                       ? Icons.view_list_rounded | 
					
						
							|  |  |  |                       : Icons.view_agenda_rounded, | 
					
						
							|  |  |  |               size: 18, | 
					
						
							|  |  |  |             )), | 
					
						
							| 
									
										
										
										
											2023-11-24 16:40:18 +08:00
										 |  |  |         onTapDown: (details) { | 
					
						
							|  |  |  |           final x = details.globalPosition.dx; | 
					
						
							|  |  |  |           final y = details.globalPosition.dy; | 
					
						
							|  |  |  |           menuPos = RelativeRect.fromLTRB(x, y, x, y); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         onTap: () => showMenu( | 
					
						
							|  |  |  |               context: context, | 
					
						
							|  |  |  |               position: menuPos, | 
					
						
							|  |  |  |               items: items, | 
					
						
							|  |  |  |               elevation: 8, | 
					
						
							|  |  |  |             )); | 
					
						
							| 
									
										
										
										
											2023-10-26 04:02:34 +05:30
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-03 20:53:42 +01:00
										 |  |  | class PeerSortDropdown extends StatefulWidget { | 
					
						
							|  |  |  |   const PeerSortDropdown({super.key}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   State<PeerSortDropdown> createState() => _PeerSortDropdownState(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _PeerSortDropdownState extends State<PeerSortDropdown> { | 
					
						
							| 
									
										
										
										
											2023-03-08 21:04:03 +01:00
										 |  |  |   @override | 
					
						
							|  |  |  |   void initState() { | 
					
						
							|  |  |  |     if (!PeerSortType.values.contains(peerSort.value)) { | 
					
						
							|  |  |  |       peerSort.value = PeerSortType.remoteId; | 
					
						
							| 
									
										
										
										
											2023-08-10 22:27:35 +08:00
										 |  |  |       bind.setLocalFlutterOption( | 
					
						
							| 
									
										
										
										
											2023-03-08 21:04:03 +01:00
										 |  |  |         k: "peer-sorting", | 
					
						
							|  |  |  |         v: peerSort.value, | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     super.initState(); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-03-03 20:53:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							| 
									
										
										
										
											2023-06-11 15:28:41 +08:00
										 |  |  |     final style = TextStyle( | 
					
						
							| 
									
										
										
										
											2023-06-10 07:08:10 +08:00
										 |  |  |         color: Theme.of(context).textTheme.titleLarge?.color, | 
					
						
							|  |  |  |         fontSize: MenuConfig.fontSize, | 
					
						
							|  |  |  |         fontWeight: FontWeight.normal); | 
					
						
							| 
									
										
										
										
											2023-06-10 06:56:45 +08:00
										 |  |  |     List<PopupMenuEntry> items = List.empty(growable: true); | 
					
						
							|  |  |  |     items.add(PopupMenuItem( | 
					
						
							| 
									
										
										
										
											2023-06-11 16:32:22 +08:00
										 |  |  |         height: 36, | 
					
						
							|  |  |  |         enabled: false, | 
					
						
							|  |  |  |         child: Text(translate("Sort by"), style: style))); | 
					
						
							| 
									
										
										
										
											2023-06-10 06:56:45 +08:00
										 |  |  |     for (var e in PeerSortType.values) { | 
					
						
							|  |  |  |       items.add(PopupMenuItem( | 
					
						
							| 
									
										
										
										
											2023-06-11 16:32:22 +08:00
										 |  |  |           height: 36, | 
					
						
							|  |  |  |           child: Obx(() => Center( | 
					
						
							|  |  |  |                 child: SizedBox( | 
					
						
							|  |  |  |                   height: 36, | 
					
						
							|  |  |  |                   child: getRadio( | 
					
						
							|  |  |  |                       Text(translate(e), style: style), e, peerSort.value, | 
					
						
							|  |  |  |                       dense: true, (String? v) async { | 
					
						
							|  |  |  |                     if (v != null) { | 
					
						
							|  |  |  |                       peerSort.value = v; | 
					
						
							| 
									
										
										
										
											2023-08-10 22:27:35 +08:00
										 |  |  |                       await bind.setLocalFlutterOption( | 
					
						
							| 
									
										
										
										
											2023-06-11 16:32:22 +08:00
										 |  |  |                         k: "peer-sorting", | 
					
						
							|  |  |  |                         v: peerSort.value, | 
					
						
							|  |  |  |                       ); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                   }), | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |               )))); | 
					
						
							| 
									
										
										
										
											2023-06-10 06:56:45 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-03-13 09:05:38 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-11 15:28:41 +08:00
										 |  |  |     var menuPos = RelativeRect.fromLTRB(0, 0, 0, 0); | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |     return _hoverAction( | 
					
						
							|  |  |  |       context: context, | 
					
						
							| 
									
										
										
										
											2023-08-12 12:51:30 +05:30
										 |  |  |       child: Tooltip( | 
					
						
							| 
									
										
										
										
											2023-08-13 14:09:41 +02:00
										 |  |  |           message: translate('Sort by'), | 
					
						
							| 
									
										
										
										
											2023-08-12 12:51:30 +05:30
										 |  |  |           child: Icon( | 
					
						
							|  |  |  |             Icons.sort_rounded, | 
					
						
							|  |  |  |             size: 18, | 
					
						
							| 
									
										
										
										
											2023-08-16 08:59:50 +08:00
										 |  |  |           )), | 
					
						
							| 
									
										
										
										
											2023-06-10 06:56:45 +08:00
										 |  |  |       onTapDown: (details) { | 
					
						
							|  |  |  |         final x = details.globalPosition.dx; | 
					
						
							|  |  |  |         final y = details.globalPosition.dy; | 
					
						
							| 
									
										
										
										
											2023-06-11 15:28:41 +08:00
										 |  |  |         menuPos = RelativeRect.fromLTRB(x, y, x, y); | 
					
						
							| 
									
										
										
										
											2023-06-10 06:56:45 +08:00
										 |  |  |       }, | 
					
						
							| 
									
										
										
										
											2023-06-11 15:28:41 +08:00
										 |  |  |       onTap: () => showMenu( | 
					
						
							|  |  |  |         context: context, | 
					
						
							|  |  |  |         position: menuPos, | 
					
						
							|  |  |  |         items: items, | 
					
						
							|  |  |  |         elevation: 8, | 
					
						
							|  |  |  |       ), | 
					
						
							| 
									
										
										
										
											2023-03-03 20:53:42 +01:00
										 |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | class RefreshWidget extends StatefulWidget { | 
					
						
							|  |  |  |   final VoidCallback onPressed; | 
					
						
							|  |  |  |   final Widget child; | 
					
						
							|  |  |  |   final RxBool? spinning; | 
					
						
							|  |  |  |   const RefreshWidget( | 
					
						
							|  |  |  |       {super.key, required this.onPressed, required this.child, this.spinning}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   State<RefreshWidget> createState() => RefreshWidgetState(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class RefreshWidgetState extends State<RefreshWidget> { | 
					
						
							|  |  |  |   double turns = 0.0; | 
					
						
							|  |  |  |   bool hover = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void initState() { | 
					
						
							|  |  |  |     super.initState(); | 
					
						
							|  |  |  |     widget.spinning?.listen((v) { | 
					
						
							|  |  |  |       if (v && mounted) { | 
					
						
							|  |  |  |         setState(() { | 
					
						
							|  |  |  |           turns += 1; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |     final deco = BoxDecoration( | 
					
						
							|  |  |  |       color: Theme.of(context).colorScheme.background, | 
					
						
							|  |  |  |       borderRadius: BorderRadius.circular(6), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     return AnimatedRotation( | 
					
						
							|  |  |  |         turns: turns, | 
					
						
							|  |  |  |         duration: const Duration(milliseconds: 200), | 
					
						
							|  |  |  |         onEnd: () { | 
					
						
							|  |  |  |           if (widget.spinning?.value == true && mounted) { | 
					
						
							|  |  |  |             setState(() => turns += 1.0); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         child: Container( | 
					
						
							|  |  |  |           padding: EdgeInsets.all(4.0), | 
					
						
							|  |  |  |           margin: EdgeInsets.symmetric(horizontal: 1), | 
					
						
							|  |  |  |           decoration: hover ? deco : null, | 
					
						
							|  |  |  |           child: InkWell( | 
					
						
							|  |  |  |               onTap: () { | 
					
						
							|  |  |  |                 if (mounted) setState(() => turns += 1.0); | 
					
						
							|  |  |  |                 widget.onPressed(); | 
					
						
							|  |  |  |               }, | 
					
						
							|  |  |  |               onHover: (value) { | 
					
						
							|  |  |  |                 if (mounted) { | 
					
						
							|  |  |  |                   setState(() { | 
					
						
							|  |  |  |                     hover = value; | 
					
						
							|  |  |  |                   }); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |               }, | 
					
						
							|  |  |  |               child: widget.child), | 
					
						
							|  |  |  |         )); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Widget _hoverAction( | 
					
						
							|  |  |  |     {required BuildContext context, | 
					
						
							|  |  |  |     required Widget child, | 
					
						
							|  |  |  |     required Function() onTap, | 
					
						
							|  |  |  |     GestureTapDownCallback? onTapDown, | 
					
						
							|  |  |  |     RxBool? hoverableWhenfalse, | 
					
						
							|  |  |  |     EdgeInsetsGeometry padding = const EdgeInsets.all(4.0)}) { | 
					
						
							|  |  |  |   final hover = false.obs; | 
					
						
							|  |  |  |   final deco = BoxDecoration( | 
					
						
							|  |  |  |     color: Theme.of(context).colorScheme.background, | 
					
						
							|  |  |  |     borderRadius: BorderRadius.circular(6), | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  |   return Obx( | 
					
						
							|  |  |  |     () => Container( | 
					
						
							|  |  |  |         margin: EdgeInsets.symmetric(horizontal: 1), | 
					
						
							|  |  |  |         decoration: | 
					
						
							|  |  |  |             (hover.value || hoverableWhenfalse?.value == false) ? deco : null, | 
					
						
							|  |  |  |         child: InkWell( | 
					
						
							|  |  |  |             onHover: (value) => hover.value = value, | 
					
						
							|  |  |  |             onTap: onTap, | 
					
						
							|  |  |  |             onTapDown: onTapDown, | 
					
						
							| 
									
										
										
										
											2023-08-20 17:14:52 +08:00
										 |  |  |             child: Container(padding: padding, child: child))), | 
					
						
							| 
									
										
										
										
											2023-08-19 08:20:48 +08:00
										 |  |  |   ); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-10-02 19:32:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | class PullDownMenuEntryImpl extends StatelessWidget | 
					
						
							|  |  |  |     implements PullDownMenuEntry { | 
					
						
							|  |  |  |   final Widget child; | 
					
						
							|  |  |  |   const PullDownMenuEntryImpl({super.key, required this.child}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |     return child; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |