| 
									
										
										
										
											2023-09-25 13:35:01 +08:00
										 |  |  | import 'dart:math'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-20 17:45:38 +08:00
										 |  |  | import 'package:dynamic_layouts/dynamic_layouts.dart'; | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | import 'package:flutter/material.dart'; | 
					
						
							| 
									
										
										
										
											2022-11-27 12:16:45 +08:00
										 |  |  | import 'package:flutter_hbb/common/formatter/id_formatter.dart'; | 
					
						
							| 
									
										
										
										
											2022-10-08 19:28:20 +09:00
										 |  |  | import 'package:flutter_hbb/common/widgets/peer_card.dart'; | 
					
						
							| 
									
										
										
										
											2022-09-22 15:35:46 +08:00
										 |  |  | import 'package:flutter_hbb/common/widgets/peers_view.dart'; | 
					
						
							| 
									
										
										
										
											2022-10-08 19:28:20 +09:00
										 |  |  | import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; | 
					
						
							| 
									
										
										
										
											2023-07-26 19:53:57 +08:00
										 |  |  | import 'package:flutter_hbb/models/ab_model.dart'; | 
					
						
							|  |  |  | import 'package:flutter_hbb/models/platform_model.dart'; | 
					
						
							| 
									
										
										
										
											2022-10-08 19:28:20 +09:00
										 |  |  | import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | import 'package:get/get.dart'; | 
					
						
							| 
									
										
										
										
											2023-08-22 19:07:01 +08:00
										 |  |  | import 'package:flex_color_picker/flex_color_picker.dart'; | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import '../../common.dart'; | 
					
						
							| 
									
										
										
										
											2023-08-10 10:08:33 +08:00
										 |  |  | import 'dialog.dart'; | 
					
						
							| 
									
										
										
										
											2023-01-08 23:30:34 +09:00
										 |  |  | import 'login.dart'; | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-29 15:18:35 +08:00
										 |  |  | final hideAbTagsPanel = false.obs; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | class AddressBook extends StatefulWidget { | 
					
						
							| 
									
										
										
										
											2022-09-23 12:20:40 +08:00
										 |  |  |   final EdgeInsets? menuPadding; | 
					
						
							|  |  |  |   const AddressBook({Key? key, this.menuPadding}) : super(key: key); | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   State<StatefulWidget> createState() { | 
					
						
							|  |  |  |     return _AddressBookState(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _AddressBookState extends State<AddressBook> { | 
					
						
							| 
									
										
										
										
											2022-10-10 18:27:26 +09:00
										 |  |  |   var menuPos = RelativeRect.fill; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |   @override | 
					
						
							|  |  |  |   void initState() { | 
					
						
							|  |  |  |     super.initState(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							| 
									
										
										
										
											2023-06-21 18:28:52 +08:00
										 |  |  |   Widget build(BuildContext context) => Obx(() { | 
					
						
							| 
									
										
										
										
											2023-09-14 10:17:03 +08:00
										 |  |  |         if (!gFFI.userModel.isLogin) { | 
					
						
							| 
									
										
										
										
											2023-06-21 18:28:52 +08:00
										 |  |  |           return Center( | 
					
						
							|  |  |  |               child: ElevatedButton( | 
					
						
							|  |  |  |                   onPressed: loginDialog, child: Text(translate("Login")))); | 
					
						
							|  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2023-08-11 15:27:50 +08:00
										 |  |  |           if (gFFI.abModel.abLoading.value && gFFI.abModel.emtpy) { | 
					
						
							| 
									
										
										
										
											2023-06-21 18:28:52 +08:00
										 |  |  |             return const Center( | 
					
						
							|  |  |  |               child: CircularProgressIndicator(), | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2023-08-11 15:27:50 +08:00
										 |  |  |           return Column( | 
					
						
							|  |  |  |             children: [ | 
					
						
							| 
									
										
										
										
											2023-08-18 16:13:24 +08:00
										 |  |  |               // NOT use Offstage to wrap LinearProgressIndicator
 | 
					
						
							| 
									
										
										
										
											2023-08-18 12:52:50 +08:00
										 |  |  |               if (gFFI.abModel.retrying.value) LinearProgressIndicator(), | 
					
						
							| 
									
										
										
										
											2023-09-14 10:17:03 +08:00
										 |  |  |               buildErrorBanner(context, | 
					
						
							|  |  |  |                   loading: gFFI.abModel.abLoading, | 
					
						
							| 
									
										
										
										
											2023-08-13 14:46:04 +08:00
										 |  |  |                   err: gFFI.abModel.pullError, | 
					
						
							|  |  |  |                   retry: null, | 
					
						
							|  |  |  |                   close: () => gFFI.abModel.pullError.value = ''), | 
					
						
							| 
									
										
										
										
											2023-09-14 10:17:03 +08:00
										 |  |  |               buildErrorBanner(context, | 
					
						
							|  |  |  |                   loading: gFFI.abModel.abLoading, | 
					
						
							| 
									
										
										
										
											2023-08-13 14:46:04 +08:00
										 |  |  |                   err: gFFI.abModel.pushError, | 
					
						
							| 
									
										
										
										
											2023-08-16 10:18:29 +08:00
										 |  |  |                   retry: () => gFFI.abModel.pushAb(isRetry: true), | 
					
						
							| 
									
										
										
										
											2023-08-13 14:46:04 +08:00
										 |  |  |                   close: () => gFFI.abModel.pushError.value = ''), | 
					
						
							| 
									
										
										
										
											2023-08-11 15:27:50 +08:00
										 |  |  |               Expanded( | 
					
						
							|  |  |  |                   child: isDesktop | 
					
						
							|  |  |  |                       ? _buildAddressBookDesktop() | 
					
						
							|  |  |  |                       : _buildAddressBookMobile()) | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |           ); | 
					
						
							| 
									
										
										
										
											2022-10-09 19:41:50 +09:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-06-21 18:28:52 +08:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-10 18:27:26 +09:00
										 |  |  |   Widget _buildAddressBookDesktop() { | 
					
						
							| 
									
										
										
										
											2022-10-08 17:13:24 +09:00
										 |  |  |     return Row( | 
					
						
							|  |  |  |       children: [ | 
					
						
							| 
									
										
										
										
											2023-06-29 15:18:35 +08:00
										 |  |  |         Offstage( | 
					
						
							| 
									
										
										
										
											2023-07-26 19:53:57 +08:00
										 |  |  |             offstage: hideAbTagsPanel.value, | 
					
						
							|  |  |  |             child: Container( | 
					
						
							|  |  |  |               decoration: BoxDecoration( | 
					
						
							|  |  |  |                   borderRadius: BorderRadius.circular(12), | 
					
						
							|  |  |  |                   border: Border.all( | 
					
						
							|  |  |  |                       color: Theme.of(context).colorScheme.background)), | 
					
						
							|  |  |  |               child: Container( | 
					
						
							|  |  |  |                 width: 150, | 
					
						
							|  |  |  |                 height: double.infinity, | 
					
						
							|  |  |  |                 padding: const EdgeInsets.all(8.0), | 
					
						
							|  |  |  |                 child: Column( | 
					
						
							|  |  |  |                   children: [ | 
					
						
							|  |  |  |                     _buildTagHeader().marginOnly(left: 8.0, right: 0), | 
					
						
							|  |  |  |                     Expanded( | 
					
						
							|  |  |  |                       child: Container( | 
					
						
							|  |  |  |                         width: double.infinity, | 
					
						
							|  |  |  |                         height: double.infinity, | 
					
						
							|  |  |  |                         child: _buildTags(), | 
					
						
							|  |  |  |                       ), | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                   ], | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |               ), | 
					
						
							|  |  |  |             ).marginOnly(right: 12.0)), | 
					
						
							| 
									
										
										
										
											2022-10-10 18:27:26 +09:00
										 |  |  |         _buildPeersViews() | 
					
						
							| 
									
										
										
										
											2022-10-08 17:13:24 +09:00
										 |  |  |       ], | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-10 18:27:26 +09:00
										 |  |  |   Widget _buildAddressBookMobile() { | 
					
						
							|  |  |  |     return Column( | 
					
						
							|  |  |  |       children: [ | 
					
						
							| 
									
										
										
										
											2023-07-26 19:57:42 +08:00
										 |  |  |         Offstage( | 
					
						
							|  |  |  |             offstage: hideAbTagsPanel.value, | 
					
						
							|  |  |  |             child: Container( | 
					
						
							|  |  |  |               decoration: BoxDecoration( | 
					
						
							|  |  |  |                   borderRadius: BorderRadius.circular(6), | 
					
						
							|  |  |  |                   border: Border.all( | 
					
						
							|  |  |  |                       color: Theme.of(context).colorScheme.background)), | 
					
						
							|  |  |  |               child: Container( | 
					
						
							|  |  |  |                 padding: const EdgeInsets.all(8.0), | 
					
						
							|  |  |  |                 child: Column( | 
					
						
							|  |  |  |                   mainAxisSize: MainAxisSize.min, | 
					
						
							|  |  |  |                   children: [ | 
					
						
							|  |  |  |                     _buildTagHeader().marginOnly(left: 8.0, right: 0), | 
					
						
							|  |  |  |                     Container( | 
					
						
							|  |  |  |                       width: double.infinity, | 
					
						
							|  |  |  |                       child: _buildTags(), | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                   ], | 
					
						
							| 
									
										
										
										
											2023-06-22 11:52:25 +08:00
										 |  |  |                 ), | 
					
						
							| 
									
										
										
										
											2023-07-26 19:57:42 +08:00
										 |  |  |               ), | 
					
						
							|  |  |  |             ).marginOnly(bottom: 12.0)), | 
					
						
							| 
									
										
										
										
											2022-10-10 18:27:26 +09:00
										 |  |  |         _buildPeersViews() | 
					
						
							|  |  |  |       ], | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Widget _buildTagHeader() { | 
					
						
							|  |  |  |     return Row( | 
					
						
							|  |  |  |       mainAxisAlignment: MainAxisAlignment.spaceBetween, | 
					
						
							|  |  |  |       children: [ | 
					
						
							|  |  |  |         Text(translate('Tags')), | 
					
						
							| 
									
										
										
										
											2023-02-21 16:29:06 +08:00
										 |  |  |         Listener( | 
					
						
							|  |  |  |             onPointerDown: (e) { | 
					
						
							|  |  |  |               final x = e.position.dx; | 
					
						
							|  |  |  |               final y = e.position.dy; | 
					
						
							| 
									
										
										
										
											2022-10-10 18:27:26 +09:00
										 |  |  |               menuPos = RelativeRect.fromLTRB(x, y, x, y); | 
					
						
							|  |  |  |             }, | 
					
						
							| 
									
										
										
										
											2023-02-21 16:29:06 +08:00
										 |  |  |             onPointerUp: (_) => _showMenu(menuPos), | 
					
						
							| 
									
										
										
										
											2023-06-21 19:39:55 +08:00
										 |  |  |             child: build_more(context, invert: true)), | 
					
						
							| 
									
										
										
										
											2022-10-10 18:27:26 +09:00
										 |  |  |       ], | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Widget _buildTags() { | 
					
						
							| 
									
										
										
										
											2023-07-26 19:53:57 +08:00
										 |  |  |     return Obx(() { | 
					
						
							|  |  |  |       final List tags; | 
					
						
							|  |  |  |       if (gFFI.abModel.sortTags.value) { | 
					
						
							|  |  |  |         tags = gFFI.abModel.tags.toList(); | 
					
						
							|  |  |  |         tags.sort(); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         tags = gFFI.abModel.tags; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-09-24 19:54:11 +08:00
										 |  |  |       tagBuilder(String e) { | 
					
						
							|  |  |  |         return AddressBookTag( | 
					
						
							|  |  |  |             name: e, | 
					
						
							|  |  |  |             tags: gFFI.abModel.selectedTags, | 
					
						
							|  |  |  |             onTap: () { | 
					
						
							|  |  |  |               if (gFFI.abModel.selectedTags.contains(e)) { | 
					
						
							|  |  |  |                 gFFI.abModel.selectedTags.remove(e); | 
					
						
							|  |  |  |               } else { | 
					
						
							|  |  |  |                 gFFI.abModel.selectedTags.add(e); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-25 13:35:01 +08:00
										 |  |  |       final gridView = DynamicGridView.builder( | 
					
						
							|  |  |  |           shrinkWrap: isMobile, | 
					
						
							|  |  |  |           gridDelegate: SliverGridDelegateWithWrapping(), | 
					
						
							|  |  |  |           itemCount: tags.length, | 
					
						
							|  |  |  |           itemBuilder: (BuildContext context, int index) { | 
					
						
							|  |  |  |             final e = tags[index]; | 
					
						
							|  |  |  |             return tagBuilder(e); | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |       final maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0); | 
					
						
							| 
									
										
										
										
											2023-09-24 19:54:11 +08:00
										 |  |  |       return isDesktop | 
					
						
							| 
									
										
										
										
											2023-09-25 13:35:01 +08:00
										 |  |  |           ? gridView | 
					
						
							|  |  |  |           : LimitedBox(maxHeight: maxHeight, child: gridView); | 
					
						
							| 
									
										
										
										
											2023-07-26 19:53:57 +08:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-10-10 18:27:26 +09:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Widget _buildPeersViews() { | 
					
						
							|  |  |  |     return Expanded( | 
					
						
							|  |  |  |       child: Align( | 
					
						
							|  |  |  |           alignment: Alignment.topLeft, | 
					
						
							| 
									
										
										
										
											2023-09-14 10:17:03 +08:00
										 |  |  |           child: AddressBookPeersView( | 
					
						
							|  |  |  |             menuPadding: widget.menuPadding, | 
					
						
							|  |  |  |             initPeers: gFFI.abModel.peers, | 
					
						
							|  |  |  |           )), | 
					
						
							| 
									
										
										
										
											2022-10-10 18:27:26 +09:00
										 |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-26 19:53:57 +08:00
										 |  |  |   @protected | 
					
						
							|  |  |  |   MenuEntryBase<String> syncMenuItem() { | 
					
						
							|  |  |  |     return MenuEntrySwitch<String>( | 
					
						
							|  |  |  |       switchType: SwitchType.scheckbox, | 
					
						
							|  |  |  |       text: translate('Sync with recent sessions'), | 
					
						
							|  |  |  |       getter: () async { | 
					
						
							|  |  |  |         return shouldSyncAb(); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       setter: (bool v) async { | 
					
						
							|  |  |  |         bind.mainSetLocalOption(key: syncAbOption, value: v ? 'Y' : ''); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       dismissOnClicked: true, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @protected | 
					
						
							|  |  |  |   MenuEntryBase<String> sortMenuItem() { | 
					
						
							|  |  |  |     return MenuEntrySwitch<String>( | 
					
						
							|  |  |  |       switchType: SwitchType.scheckbox, | 
					
						
							|  |  |  |       text: translate('Sort tags'), | 
					
						
							|  |  |  |       getter: () async { | 
					
						
							|  |  |  |         return shouldSortTags(); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       setter: (bool v) async { | 
					
						
							|  |  |  |         bind.mainSetLocalOption(key: sortAbTagsOption, value: v ? 'Y' : ''); | 
					
						
							|  |  |  |         gFFI.abModel.sortTags.value = v; | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       dismissOnClicked: true, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-11 16:50:48 +08:00
										 |  |  |   @protected | 
					
						
							|  |  |  |   MenuEntryBase<String> filterMenuItem() { | 
					
						
							|  |  |  |     return MenuEntrySwitch<String>( | 
					
						
							|  |  |  |       switchType: SwitchType.scheckbox, | 
					
						
							|  |  |  |       text: translate('Filter by intersection'), | 
					
						
							|  |  |  |       getter: () async { | 
					
						
							|  |  |  |         return filterAbTagByIntersection(); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       setter: (bool v) async { | 
					
						
							|  |  |  |         bind.mainSetLocalOption(key: filterAbTagOption, value: v ? 'Y' : ''); | 
					
						
							|  |  |  |         gFFI.abModel.filterByIntersection.value = v; | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       dismissOnClicked: true, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-08 19:28:20 +09:00
										 |  |  |   void _showMenu(RelativeRect pos) { | 
					
						
							|  |  |  |     final items = [ | 
					
						
							|  |  |  |       getEntry(translate("Add ID"), abAddId), | 
					
						
							|  |  |  |       getEntry(translate("Add Tag"), abAddTag), | 
					
						
							|  |  |  |       getEntry(translate("Unselect all tags"), gFFI.abModel.unsetSelectedTags), | 
					
						
							| 
									
										
										
										
											2023-07-26 19:53:57 +08:00
										 |  |  |       sortMenuItem(), | 
					
						
							|  |  |  |       syncMenuItem(), | 
					
						
							| 
									
										
										
										
											2023-10-11 16:50:48 +08:00
										 |  |  |       filterMenuItem(), | 
					
						
							| 
									
										
										
										
											2022-10-08 19:28:20 +09:00
										 |  |  |     ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     mod_menu.showMenu( | 
					
						
							|  |  |  |       context: context, | 
					
						
							|  |  |  |       position: pos, | 
					
						
							|  |  |  |       items: items | 
					
						
							|  |  |  |           .map((e) => e.build( | 
					
						
							|  |  |  |               context, | 
					
						
							|  |  |  |               MenuConfig( | 
					
						
							|  |  |  |                   commonColor: CustomPopupMenuTheme.commonColor, | 
					
						
							|  |  |  |                   height: CustomPopupMenuTheme.height, | 
					
						
							|  |  |  |                   dividerHeight: CustomPopupMenuTheme.dividerHeight))) | 
					
						
							|  |  |  |           .expand((i) => i) | 
					
						
							|  |  |  |           .toList(), | 
					
						
							|  |  |  |       elevation: 8, | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void abAddId() async { | 
					
						
							| 
									
										
										
										
											2023-07-26 20:43:18 +08:00
										 |  |  |     if (gFFI.abModel.isFull(true)) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |     var isInProgress = false; | 
					
						
							| 
									
										
										
										
											2022-11-27 12:16:45 +08:00
										 |  |  |     IDTextEditingController idController = IDTextEditingController(text: ''); | 
					
						
							|  |  |  |     TextEditingController aliasController = TextEditingController(text: ''); | 
					
						
							|  |  |  |     final tags = List.of(gFFI.abModel.tags); | 
					
						
							|  |  |  |     var selectedTag = List<dynamic>.empty(growable: true).obs; | 
					
						
							|  |  |  |     final style = TextStyle(fontSize: 14.0); | 
					
						
							|  |  |  |     String? errorMsg; | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-08 12:34:19 +08:00
										 |  |  |     gFFI.dialogManager.show((setState, close, context) { | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |       submit() async { | 
					
						
							|  |  |  |         setState(() { | 
					
						
							|  |  |  |           isInProgress = true; | 
					
						
							| 
									
										
										
										
											2022-11-27 12:16:45 +08:00
										 |  |  |           errorMsg = null; | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |         }); | 
					
						
							| 
									
										
										
										
											2022-11-27 12:16:45 +08:00
										 |  |  |         String id = idController.id; | 
					
						
							|  |  |  |         if (id.isEmpty) { | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |           // pass
 | 
					
						
							|  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2022-11-27 12:16:45 +08:00
										 |  |  |           if (gFFI.abModel.idContainBy(id)) { | 
					
						
							|  |  |  |             setState(() { | 
					
						
							|  |  |  |               isInProgress = false; | 
					
						
							|  |  |  |               errorMsg = translate('ID already exists'); | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |             return; | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |           } | 
					
						
							| 
									
										
										
										
											2022-11-27 12:16:45 +08:00
										 |  |  |           gFFI.abModel.addId(id, aliasController.text.trim(), selectedTag); | 
					
						
							| 
									
										
										
										
											2023-08-16 10:18:29 +08:00
										 |  |  |           gFFI.abModel.pushAb(); | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |           this.setState(() {}); | 
					
						
							|  |  |  |           // final currentPeers
 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         close(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-15 10:11:04 +08:00
										 |  |  |       double marginBottom = 4; | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |       return CustomAlertDialog( | 
					
						
							|  |  |  |         title: Text(translate("Add ID")), | 
					
						
							|  |  |  |         content: Column( | 
					
						
							|  |  |  |           crossAxisAlignment: CrossAxisAlignment.start, | 
					
						
							|  |  |  |           children: [ | 
					
						
							| 
									
										
										
										
											2022-11-27 12:16:45 +08:00
										 |  |  |             Column( | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |               children: [ | 
					
						
							| 
									
										
										
										
											2022-11-27 12:16:45 +08:00
										 |  |  |                 Align( | 
					
						
							|  |  |  |                   alignment: Alignment.centerLeft, | 
					
						
							|  |  |  |                   child: Row( | 
					
						
							|  |  |  |                     children: [ | 
					
						
							|  |  |  |                       Text( | 
					
						
							|  |  |  |                         '*', | 
					
						
							|  |  |  |                         style: TextStyle(color: Colors.red, fontSize: 14), | 
					
						
							|  |  |  |                       ), | 
					
						
							|  |  |  |                       Text( | 
					
						
							|  |  |  |                         'ID', | 
					
						
							|  |  |  |                         style: style, | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |                       ), | 
					
						
							| 
									
										
										
										
											2022-11-27 12:16:45 +08:00
										 |  |  |                     ], | 
					
						
							|  |  |  |                   ), | 
					
						
							| 
									
										
										
										
											2023-08-15 10:11:04 +08:00
										 |  |  |                 ).marginOnly(bottom: marginBottom), | 
					
						
							| 
									
										
										
										
											2022-11-27 12:16:45 +08:00
										 |  |  |                 TextField( | 
					
						
							|  |  |  |                   controller: idController, | 
					
						
							|  |  |  |                   inputFormatters: [IDTextInputFormatter()], | 
					
						
							| 
									
										
										
										
											2023-03-17 19:05:43 +01:00
										 |  |  |                   decoration: InputDecoration(errorText: errorMsg), | 
					
						
							| 
									
										
										
										
											2022-11-27 12:16:45 +08:00
										 |  |  |                 ), | 
					
						
							|  |  |  |                 Align( | 
					
						
							|  |  |  |                   alignment: Alignment.centerLeft, | 
					
						
							|  |  |  |                   child: Text( | 
					
						
							|  |  |  |                     translate('Alias'), | 
					
						
							|  |  |  |                     style: style, | 
					
						
							|  |  |  |                   ), | 
					
						
							| 
									
										
										
										
											2023-08-15 10:11:04 +08:00
										 |  |  |                 ).marginOnly(top: 8, bottom: marginBottom), | 
					
						
							| 
									
										
										
										
											2022-11-27 12:16:45 +08:00
										 |  |  |                 TextField( | 
					
						
							|  |  |  |                   controller: aliasController, | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |                 Align( | 
					
						
							|  |  |  |                   alignment: Alignment.centerLeft, | 
					
						
							|  |  |  |                   child: Text( | 
					
						
							|  |  |  |                     translate('Tags'), | 
					
						
							|  |  |  |                     style: style, | 
					
						
							|  |  |  |                   ), | 
					
						
							| 
									
										
										
										
											2023-08-15 10:11:04 +08:00
										 |  |  |                 ).marginOnly(top: 8, bottom: marginBottom), | 
					
						
							|  |  |  |                 Align( | 
					
						
							|  |  |  |                   alignment: Alignment.centerLeft, | 
					
						
							| 
									
										
										
										
											2022-11-27 12:16:45 +08:00
										 |  |  |                   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), | 
					
						
							|  |  |  |                   ), | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |                 ), | 
					
						
							|  |  |  |               ], | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             const SizedBox( | 
					
						
							|  |  |  |               height: 4.0, | 
					
						
							|  |  |  |             ), | 
					
						
							| 
									
										
										
										
											2023-08-18 16:13:24 +08:00
										 |  |  |             // NOT use Offstage to wrap LinearProgressIndicator
 | 
					
						
							|  |  |  |             if (isInProgress) const LinearProgressIndicator(), | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |           ], | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         actions: [ | 
					
						
							| 
									
										
										
										
											2023-01-15 19:46:16 +08:00
										 |  |  |           dialogButton("Cancel", onPressed: close, isOutline: true), | 
					
						
							|  |  |  |           dialogButton("OK", onPressed: submit), | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |         ], | 
					
						
							|  |  |  |         onSubmit: submit, | 
					
						
							|  |  |  |         onCancel: close, | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void abAddTag() async { | 
					
						
							|  |  |  |     var field = ""; | 
					
						
							|  |  |  |     var msg = ""; | 
					
						
							|  |  |  |     var isInProgress = false; | 
					
						
							|  |  |  |     TextEditingController controller = TextEditingController(text: field); | 
					
						
							| 
									
										
										
										
											2023-05-08 12:34:19 +08:00
										 |  |  |     gFFI.dialogManager.show((setState, close, context) { | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |       submit() async { | 
					
						
							|  |  |  |         setState(() { | 
					
						
							|  |  |  |           msg = ""; | 
					
						
							|  |  |  |           isInProgress = true; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |         field = controller.text.trim(); | 
					
						
							|  |  |  |         if (field.isEmpty) { | 
					
						
							|  |  |  |           // pass
 | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           final tags = field.trim().split(RegExp(r"[\s,;\n]+")); | 
					
						
							|  |  |  |           field = tags.join(','); | 
					
						
							|  |  |  |           for (final tag in tags) { | 
					
						
							|  |  |  |             gFFI.abModel.addTag(tag); | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2023-08-16 10:18:29 +08:00
										 |  |  |           gFFI.abModel.pushAb(); | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |           // final currentPeers
 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         close(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return CustomAlertDialog( | 
					
						
							|  |  |  |         title: Text(translate("Add Tag")), | 
					
						
							|  |  |  |         content: Column( | 
					
						
							|  |  |  |           crossAxisAlignment: CrossAxisAlignment.start, | 
					
						
							|  |  |  |           children: [ | 
					
						
							|  |  |  |             Text(translate("whitelist_sep")), | 
					
						
							|  |  |  |             const SizedBox( | 
					
						
							|  |  |  |               height: 8.0, | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             Row( | 
					
						
							|  |  |  |               children: [ | 
					
						
							|  |  |  |                 Expanded( | 
					
						
							|  |  |  |                   child: TextField( | 
					
						
							|  |  |  |                     maxLines: null, | 
					
						
							|  |  |  |                     decoration: InputDecoration( | 
					
						
							|  |  |  |                       errorText: msg.isEmpty ? null : translate(msg), | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                     controller: controller, | 
					
						
							| 
									
										
										
										
											2023-02-12 09:03:13 +08:00
										 |  |  |                     autofocus: true, | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |                   ), | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |               ], | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             const SizedBox( | 
					
						
							|  |  |  |               height: 4.0, | 
					
						
							|  |  |  |             ), | 
					
						
							| 
									
										
										
										
											2023-08-18 16:13:24 +08:00
										 |  |  |             // NOT use Offstage to wrap LinearProgressIndicator
 | 
					
						
							|  |  |  |             if (isInProgress) const LinearProgressIndicator(), | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |           ], | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         actions: [ | 
					
						
							| 
									
										
										
										
											2023-01-15 19:46:16 +08:00
										 |  |  |           dialogButton("Cancel", onPressed: close, isOutline: true), | 
					
						
							|  |  |  |           dialogButton("OK", onPressed: submit), | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |         ], | 
					
						
							|  |  |  |         onSubmit: submit, | 
					
						
							|  |  |  |         onCancel: close, | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-10-08 17:39:05 +09:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-08 17:39:05 +09:00
										 |  |  | class AddressBookTag extends StatelessWidget { | 
					
						
							|  |  |  |   final String name; | 
					
						
							|  |  |  |   final RxList<dynamic> tags; | 
					
						
							|  |  |  |   final Function()? onTap; | 
					
						
							| 
									
										
										
										
											2022-10-08 19:52:02 +09:00
										 |  |  |   final bool showActionMenu; | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-08 17:39:05 +09:00
										 |  |  |   const AddressBookTag( | 
					
						
							|  |  |  |       {Key? key, | 
					
						
							|  |  |  |       required this.name, | 
					
						
							|  |  |  |       required this.tags, | 
					
						
							|  |  |  |       this.onTap, | 
					
						
							| 
									
										
										
										
											2022-10-08 19:52:02 +09:00
										 |  |  |       this.showActionMenu = true}) | 
					
						
							| 
									
										
										
										
											2022-10-08 17:39:05 +09:00
										 |  |  |       : super(key: key); | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-08 17:39:05 +09:00
										 |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							| 
									
										
										
										
											2022-10-08 19:52:02 +09:00
										 |  |  |     var pos = RelativeRect.fill; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     void setPosition(TapDownDetails e) { | 
					
						
							|  |  |  |       final x = e.globalPosition.dx; | 
					
						
							|  |  |  |       final y = e.globalPosition.dy; | 
					
						
							|  |  |  |       pos = RelativeRect.fromLTRB(x, y, x, y); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-13 18:13:06 +08:00
										 |  |  |     const double radius = 8; | 
					
						
							| 
									
										
										
										
											2022-10-08 19:52:02 +09:00
										 |  |  |     return GestureDetector( | 
					
						
							| 
									
										
										
										
											2022-10-08 17:39:05 +09:00
										 |  |  |       onTap: onTap, | 
					
						
							| 
									
										
										
										
											2022-10-08 19:52:02 +09:00
										 |  |  |       onTapDown: showActionMenu ? setPosition : null, | 
					
						
							|  |  |  |       onSecondaryTapDown: showActionMenu ? setPosition : null, | 
					
						
							|  |  |  |       onSecondaryTap: showActionMenu ? () => _showMenu(context, pos) : null, | 
					
						
							|  |  |  |       onLongPress: showActionMenu ? () => _showMenu(context, pos) : null, | 
					
						
							| 
									
										
										
										
											2023-08-13 18:13:06 +08:00
										 |  |  |       child: Obx(() => Container( | 
					
						
							|  |  |  |             decoration: BoxDecoration( | 
					
						
							|  |  |  |                 color: tags.contains(name) | 
					
						
							| 
									
										
										
										
											2023-08-22 19:07:01 +08:00
										 |  |  |                     ? gFFI.abModel.getTagColor(name) | 
					
						
							| 
									
										
										
										
											2023-08-13 18:13:06 +08:00
										 |  |  |                     : Theme.of(context).colorScheme.background, | 
					
						
							|  |  |  |                 borderRadius: BorderRadius.circular(4)), | 
					
						
							|  |  |  |             margin: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 4.0), | 
					
						
							|  |  |  |             padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 6.0), | 
					
						
							|  |  |  |             child: IntrinsicWidth( | 
					
						
							|  |  |  |               child: Row( | 
					
						
							|  |  |  |                 children: [ | 
					
						
							|  |  |  |                   Container( | 
					
						
							|  |  |  |                     width: radius, | 
					
						
							|  |  |  |                     height: radius, | 
					
						
							|  |  |  |                     decoration: BoxDecoration( | 
					
						
							|  |  |  |                         shape: BoxShape.circle, | 
					
						
							|  |  |  |                         color: tags.contains(name) | 
					
						
							|  |  |  |                             ? Colors.white | 
					
						
							| 
									
										
										
										
											2023-08-22 19:07:01 +08:00
										 |  |  |                             : gFFI.abModel.getTagColor(name)), | 
					
						
							| 
									
										
										
										
											2023-08-13 18:13:06 +08:00
										 |  |  |                   ).marginOnly(right: radius / 2), | 
					
						
							|  |  |  |                   Expanded( | 
					
						
							|  |  |  |                     child: Text(name, | 
					
						
							|  |  |  |                         style: TextStyle( | 
					
						
							|  |  |  |                             overflow: TextOverflow.ellipsis, | 
					
						
							|  |  |  |                             color: tags.contains(name) ? Colors.white : null)), | 
					
						
							|  |  |  |                   ), | 
					
						
							|  |  |  |                 ], | 
					
						
							|  |  |  |               ), | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |           )), | 
					
						
							| 
									
										
										
										
											2022-10-08 17:39:05 +09:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-10-08 19:52:02 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |   void _showMenu(BuildContext context, RelativeRect pos) { | 
					
						
							|  |  |  |     final items = [ | 
					
						
							| 
									
										
										
										
											2023-08-10 10:08:33 +08:00
										 |  |  |       getEntry(translate("Rename"), () { | 
					
						
							|  |  |  |         renameDialog( | 
					
						
							|  |  |  |             oldName: name, | 
					
						
							|  |  |  |             validator: (String? newName) { | 
					
						
							|  |  |  |               if (newName == null || newName.isEmpty) { | 
					
						
							|  |  |  |                 return translate('Can not be empty'); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |               if (newName != name && gFFI.abModel.tags.contains(newName)) { | 
					
						
							|  |  |  |                 return translate('Already exists'); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             onSubmit: (String newName) { | 
					
						
							|  |  |  |               if (name != newName) { | 
					
						
							|  |  |  |                 gFFI.abModel.renameTag(name, newName); | 
					
						
							|  |  |  |                 gFFI.abModel.pushAb(); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |               Future.delayed(Duration.zero, () => Get.back()); | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             onCancel: () { | 
					
						
							|  |  |  |               Future.delayed(Duration.zero, () => Get.back()); | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |       }), | 
					
						
							| 
									
										
										
										
											2023-08-22 19:07:01 +08:00
										 |  |  |       getEntry(translate(translate('Change Color')), () async { | 
					
						
							|  |  |  |         final model = gFFI.abModel; | 
					
						
							|  |  |  |         Color oldColor = model.getTagColor(name); | 
					
						
							|  |  |  |         Color newColor = await showColorPickerDialog( | 
					
						
							|  |  |  |           context, | 
					
						
							|  |  |  |           oldColor, | 
					
						
							|  |  |  |           pickersEnabled: { | 
					
						
							|  |  |  |             ColorPickerType.accent: false, | 
					
						
							|  |  |  |             ColorPickerType.wheel: true, | 
					
						
							|  |  |  |           }, | 
					
						
							| 
									
										
										
										
											2023-08-23 08:15:56 +08:00
										 |  |  |           pickerTypeLabels: { | 
					
						
							|  |  |  |             ColorPickerType.primary: translate("Primary Color"), | 
					
						
							|  |  |  |             ColorPickerType.wheel: translate("HSV Color"), | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           actionButtons: ColorPickerActionButtons( | 
					
						
							|  |  |  |               dialogOkButtonLabel: translate("OK"), | 
					
						
							|  |  |  |               dialogCancelButtonLabel: translate("Cancel")), | 
					
						
							| 
									
										
										
										
											2023-08-22 19:07:01 +08:00
										 |  |  |           showColorCode: true, | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         if (oldColor != newColor) { | 
					
						
							|  |  |  |           model.setTagColor(name, newColor); | 
					
						
							|  |  |  |           model.pushAb(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }), | 
					
						
							| 
									
										
										
										
											2022-10-08 19:52:02 +09:00
										 |  |  |       getEntry(translate("Delete"), () { | 
					
						
							|  |  |  |         gFFI.abModel.deleteTag(name); | 
					
						
							|  |  |  |         gFFI.abModel.pushAb(); | 
					
						
							|  |  |  |         Future.delayed(Duration.zero, () => Get.back()); | 
					
						
							|  |  |  |       }), | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     mod_menu.showMenu( | 
					
						
							|  |  |  |       context: context, | 
					
						
							|  |  |  |       position: pos, | 
					
						
							|  |  |  |       items: items | 
					
						
							|  |  |  |           .map((e) => e.build( | 
					
						
							|  |  |  |               context, | 
					
						
							|  |  |  |               MenuConfig( | 
					
						
							|  |  |  |                   commonColor: CustomPopupMenuTheme.commonColor, | 
					
						
							|  |  |  |                   height: CustomPopupMenuTheme.height, | 
					
						
							|  |  |  |                   dividerHeight: CustomPopupMenuTheme.dividerHeight))) | 
					
						
							|  |  |  |           .expand((i) => i) | 
					
						
							|  |  |  |           .toList(), | 
					
						
							|  |  |  |       elevation: 8, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | MenuEntryButton<String> getEntry(String title, VoidCallback proc) { | 
					
						
							|  |  |  |   return MenuEntryButton<String>( | 
					
						
							|  |  |  |     childBuilder: (TextStyle? style) => Text( | 
					
						
							|  |  |  |       title, | 
					
						
							|  |  |  |       style: style, | 
					
						
							|  |  |  |     ), | 
					
						
							|  |  |  |     proc: proc, | 
					
						
							|  |  |  |     dismissOnClicked: true, | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  | } |