| 
									
										
										
										
											2023-09-25 13:35:01 +08:00
										 |  |  | import 'dart:math'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  | import 'package:dropdown_button2/dropdown_button2.dart'; | 
					
						
							| 
									
										
										
										
											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'; | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  | import 'package:flutter_hbb/common/hbbs/hbbs.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'; | 
					
						
							| 
									
										
										
										
											2024-03-21 14:01:18 +08:00
										 |  |  | import 'package:url_launcher/url_launcher_string.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
										 |  |  |           return Column( | 
					
						
							|  |  |  |             children: [ | 
					
						
							| 
									
										
										
										
											2023-09-14 10:17:03 +08:00
										 |  |  |               buildErrorBanner(context, | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |                   loading: gFFI.abModel.currentAbLoading, | 
					
						
							|  |  |  |                   err: gFFI.abModel.currentAbPullError, | 
					
						
							| 
									
										
										
										
											2023-08-13 14:46:04 +08:00
										 |  |  |                   retry: null, | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |                   close: () => gFFI.abModel.currentAbPullError.value = ''), | 
					
						
							| 
									
										
										
										
											2023-09-14 10:17:03 +08:00
										 |  |  |               buildErrorBanner(context, | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |                   loading: gFFI.abModel.currentAbLoading, | 
					
						
							|  |  |  |                   err: gFFI.abModel.currentAbPushError, | 
					
						
							|  |  |  |                   retry: null, // remove retry
 | 
					
						
							|  |  |  |                   close: () => gFFI.abModel.currentAbPushError.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( | 
					
						
							| 
									
										
										
										
											2024-03-22 23:32:59 +08:00
										 |  |  |                 width: 200, | 
					
						
							| 
									
										
										
										
											2023-07-26 19:53:57 +08:00
										 |  |  |                 height: double.infinity, | 
					
						
							|  |  |  |                 padding: const EdgeInsets.all(8.0), | 
					
						
							|  |  |  |                 child: Column( | 
					
						
							|  |  |  |                   children: [ | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |                     _buildAbDropdown(), | 
					
						
							| 
									
										
										
										
											2023-07-26 19:53:57 +08:00
										 |  |  |                     _buildTagHeader().marginOnly(left: 8.0, right: 0), | 
					
						
							|  |  |  |                     Expanded( | 
					
						
							|  |  |  |                       child: Container( | 
					
						
							|  |  |  |                         width: double.infinity, | 
					
						
							|  |  |  |                         height: double.infinity, | 
					
						
							|  |  |  |                         child: _buildTags(), | 
					
						
							|  |  |  |                       ), | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |                     ), | 
					
						
							|  |  |  |                     _buildAbPermission(), | 
					
						
							| 
									
										
										
										
											2023-07-26 19:53:57 +08:00
										 |  |  |                   ], | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |               ), | 
					
						
							|  |  |  |             ).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: [ | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |                     _buildAbDropdown(), | 
					
						
							| 
									
										
										
										
											2023-07-26 19:57:42 +08:00
										 |  |  |                     _buildTagHeader().marginOnly(left: 8.0, right: 0), | 
					
						
							|  |  |  |                     Container( | 
					
						
							|  |  |  |                       width: double.infinity, | 
					
						
							|  |  |  |                       child: _buildTags(), | 
					
						
							|  |  |  |                     ), | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |                     _buildAbPermission(), | 
					
						
							| 
									
										
										
										
											2023-07-26 19:57:42 +08:00
										 |  |  |                   ], | 
					
						
							| 
									
										
										
										
											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() | 
					
						
							|  |  |  |       ], | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |   Widget _buildAbPermission() { | 
					
						
							|  |  |  |     icon(IconData data, String tooltip) { | 
					
						
							|  |  |  |       return Tooltip( | 
					
						
							|  |  |  |           message: translate(tooltip), | 
					
						
							|  |  |  |           waitDuration: Duration.zero, | 
					
						
							|  |  |  |           child: Icon(data, size: 12.0).marginSymmetric(horizontal: 2.0)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return Obx(() { | 
					
						
							|  |  |  |       if (gFFI.abModel.legacyMode.value) return Offstage(); | 
					
						
							|  |  |  |       if (gFFI.abModel.current.isPersonal()) { | 
					
						
							|  |  |  |         return Row( | 
					
						
							|  |  |  |           mainAxisAlignment: MainAxisAlignment.end, | 
					
						
							|  |  |  |           children: [ | 
					
						
							|  |  |  |             icon(Icons.cloud_off, "Personal"), | 
					
						
							|  |  |  |           ], | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         List<Widget> children = []; | 
					
						
							|  |  |  |         final rule = gFFI.abModel.current.sharedProfile()?.rule; | 
					
						
							|  |  |  |         if (rule == ShareRule.read.value) { | 
					
						
							|  |  |  |           children.add( | 
					
						
							|  |  |  |               icon(Icons.visibility, ShareRule.desc(ShareRule.read.value))); | 
					
						
							|  |  |  |         } else if (rule == ShareRule.readWrite.value) { | 
					
						
							|  |  |  |           children | 
					
						
							|  |  |  |               .add(icon(Icons.edit, ShareRule.desc(ShareRule.readWrite.value))); | 
					
						
							|  |  |  |         } else if (rule == ShareRule.fullControl.value) { | 
					
						
							|  |  |  |           children.add(icon( | 
					
						
							|  |  |  |               Icons.security, ShareRule.desc(ShareRule.fullControl.value))); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         final owner = gFFI.abModel.current.sharedProfile()?.owner; | 
					
						
							|  |  |  |         if (owner != null) { | 
					
						
							|  |  |  |           children.add(icon(Icons.person, "${translate("Owner")}: $owner")); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return Row( | 
					
						
							|  |  |  |           mainAxisAlignment: MainAxisAlignment.end, | 
					
						
							|  |  |  |           children: children, | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Widget _buildAbDropdown() { | 
					
						
							|  |  |  |     if (gFFI.abModel.legacyMode.value) { | 
					
						
							|  |  |  |       return Offstage(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     final names = gFFI.abModel.addressBookNames(); | 
					
						
							|  |  |  |     if (!names.contains(gFFI.abModel.currentName.value)) { | 
					
						
							|  |  |  |       return Offstage(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     final TextEditingController textEditingController = TextEditingController(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return DropdownButton2<String>( | 
					
						
							|  |  |  |       value: gFFI.abModel.currentName.value, | 
					
						
							|  |  |  |       onChanged: (value) { | 
					
						
							|  |  |  |         if (value != null) { | 
					
						
							|  |  |  |           gFFI.abModel.setCurrentName(value); | 
					
						
							|  |  |  |           bind.setLocalFlutterOption(k: 'current-ab-name', v: value); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2024-03-23 10:10:22 +08:00
										 |  |  |       underline: Container( | 
					
						
							|  |  |  |         height: 0.7, | 
					
						
							|  |  |  |         color: Theme.of(context).dividerColor.withOpacity(0.1), | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |       buttonStyleData: ButtonStyleData(height: 48), | 
					
						
							|  |  |  |       menuItemStyleData: MenuItemStyleData(height: 36), | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |       items: names | 
					
						
							|  |  |  |           .map((e) => DropdownMenuItem( | 
					
						
							|  |  |  |               value: e, | 
					
						
							|  |  |  |               child: Row( | 
					
						
							|  |  |  |                 children: [ | 
					
						
							|  |  |  |                   Expanded( | 
					
						
							|  |  |  |                     child: Tooltip( | 
					
						
							| 
									
										
										
										
											2024-03-22 23:32:59 +08:00
										 |  |  |                         waitDuration: Duration(milliseconds: 500), | 
					
						
							|  |  |  |                         message: gFFI.abModel.translatedName(e), | 
					
						
							| 
									
										
										
										
											2024-03-23 10:10:22 +08:00
										 |  |  |                         child: Text( | 
					
						
							| 
									
										
										
										
											2024-03-22 23:32:59 +08:00
										 |  |  |                           gFFI.abModel.translatedName(e), | 
					
						
							| 
									
										
										
										
											2024-03-23 10:10:22 +08:00
										 |  |  |                           style: TextStyle(fontSize: 14.0), | 
					
						
							|  |  |  |                           maxLines: 1, | 
					
						
							|  |  |  |                           overflow: TextOverflow.ellipsis, | 
					
						
							| 
									
										
										
										
											2024-03-22 23:32:59 +08:00
										 |  |  |                         )), | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |                   ), | 
					
						
							|  |  |  |                 ], | 
					
						
							|  |  |  |               ))) | 
					
						
							|  |  |  |           .toList(), | 
					
						
							|  |  |  |       isExpanded: true, | 
					
						
							|  |  |  |       dropdownSearchData: DropdownSearchData( | 
					
						
							|  |  |  |         searchController: textEditingController, | 
					
						
							|  |  |  |         searchInnerWidgetHeight: 50, | 
					
						
							|  |  |  |         searchInnerWidget: Container( | 
					
						
							|  |  |  |           height: 50, | 
					
						
							|  |  |  |           padding: const EdgeInsets.only( | 
					
						
							|  |  |  |             top: 8, | 
					
						
							|  |  |  |             bottom: 4, | 
					
						
							|  |  |  |             right: 8, | 
					
						
							|  |  |  |             left: 8, | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |           child: TextFormField( | 
					
						
							|  |  |  |             expands: true, | 
					
						
							|  |  |  |             maxLines: null, | 
					
						
							|  |  |  |             controller: textEditingController, | 
					
						
							|  |  |  |             decoration: InputDecoration( | 
					
						
							|  |  |  |               isDense: true, | 
					
						
							|  |  |  |               contentPadding: const EdgeInsets.symmetric( | 
					
						
							|  |  |  |                 horizontal: 10, | 
					
						
							|  |  |  |                 vertical: 8, | 
					
						
							|  |  |  |               ), | 
					
						
							|  |  |  |               hintText: translate('Search'), | 
					
						
							|  |  |  |               hintStyle: const TextStyle(fontSize: 12), | 
					
						
							|  |  |  |               border: OutlineInputBorder( | 
					
						
							|  |  |  |                 borderRadius: BorderRadius.circular(8), | 
					
						
							|  |  |  |               ), | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         searchMatchFn: (item, searchValue) { | 
					
						
							|  |  |  |           return item.value | 
					
						
							|  |  |  |               .toString() | 
					
						
							|  |  |  |               .toLowerCase() | 
					
						
							|  |  |  |               .contains(searchValue.toLowerCase()); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-10 18:27:26 +09:00
										 |  |  |   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) { | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |         tags = gFFI.abModel.currentAbTags.toList(); | 
					
						
							| 
									
										
										
										
											2023-07-26 19:53:57 +08:00
										 |  |  |         tags.sort(); | 
					
						
							|  |  |  |       } else { | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |         tags = gFFI.abModel.currentAbTags; | 
					
						
							| 
									
										
										
										
											2023-07-26 19:53:57 +08:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |       final editPermission = gFFI.abModel.current.canWrite(); | 
					
						
							| 
									
										
										
										
											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); | 
					
						
							|  |  |  |               } | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |             }, | 
					
						
							|  |  |  |             showActionMenu: editPermission); | 
					
						
							| 
									
										
										
										
											2023-09-24 19:54:11 +08:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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, | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |             getInitPeers: () => gFFI.abModel.currentAbPeers, | 
					
						
							| 
									
										
										
										
											2023-09-14 10:17:03 +08:00
										 |  |  |           )), | 
					
						
							| 
									
										
										
										
											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 { | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |         gFFI.abModel.setShouldAsync(v); | 
					
						
							| 
									
										
										
										
											2023-07-26 19:53:57 +08:00
										 |  |  |       }, | 
					
						
							|  |  |  |       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) { | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |     final canWrite = gFFI.abModel.current.canWrite(); | 
					
						
							| 
									
										
										
										
											2022-10-08 19:28:20 +09:00
										 |  |  |     final items = [ | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |       if (canWrite) getEntry(translate("Add ID"), addIdToCurrentAb), | 
					
						
							|  |  |  |       if (canWrite) getEntry(translate("Add Tag"), abAddTag), | 
					
						
							| 
									
										
										
										
											2022-10-08 19:28:20 +09:00
										 |  |  |       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(), | 
					
						
							| 
									
										
										
										
											2024-03-22 11:02:22 +08:00
										 |  |  |       if (!gFFI.abModel.legacyMode.value) MenuEntryDivider<String>(), | 
					
						
							|  |  |  |       if (!gFFI.abModel.legacyMode.value) | 
					
						
							|  |  |  |         getEntry(translate("ab_web_console_tip"), () async { | 
					
						
							|  |  |  |           final url = await bind.mainGetApiServer(); | 
					
						
							|  |  |  |           if (await canLaunchUrlString(url)) { | 
					
						
							|  |  |  |             launchUrlString(url); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }), | 
					
						
							| 
									
										
										
										
											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
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |   void addIdToCurrentAb() async { | 
					
						
							|  |  |  |     if (gFFI.abModel.isCurrentAbFull(true)) { | 
					
						
							| 
									
										
										
										
											2023-07-26 20:43:18 +08:00
										 |  |  |       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: ''); | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |     TextEditingController passwordController = TextEditingController(text: ''); | 
					
						
							|  |  |  |     final tags = List.of(gFFI.abModel.currentAbTags); | 
					
						
							| 
									
										
										
										
											2022-11-27 12:16:45 +08:00
										 |  |  |     var selectedTag = List<dynamic>.empty(growable: true).obs; | 
					
						
							|  |  |  |     final style = TextStyle(fontSize: 14.0); | 
					
						
							|  |  |  |     String? errorMsg; | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |     final isCurrentAbShared = !gFFI.abModel.current.isPersonal(); | 
					
						
							| 
									
										
										
										
											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 { | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |           if (gFFI.abModel.idContainByCurrent(id)) { | 
					
						
							| 
									
										
										
										
											2022-11-27 12:16:45 +08:00
										 |  |  |             setState(() { | 
					
						
							|  |  |  |               isInProgress = false; | 
					
						
							|  |  |  |               errorMsg = translate('ID already exists'); | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |             return; | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |           } | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |           var password = ''; | 
					
						
							|  |  |  |           if (isCurrentAbShared) { | 
					
						
							|  |  |  |             password = passwordController.text; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |           String? errMsg2 = await gFFI.abModel.addIdToCurrent( | 
					
						
							|  |  |  |               id, aliasController.text.trim(), password, selectedTag); | 
					
						
							|  |  |  |           if (errMsg2 != null) { | 
					
						
							|  |  |  |             setState(() { | 
					
						
							|  |  |  |               isInProgress = false; | 
					
						
							|  |  |  |               errorMsg = errMsg2; | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2022-09-19 20:26:39 +08:00
										 |  |  |           // 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()], | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |                   decoration: | 
					
						
							|  |  |  |                       InputDecoration(errorText: errorMsg, errorMaxLines: 5), | 
					
						
							| 
									
										
										
										
											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, | 
					
						
							|  |  |  |                 ), | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |                 if (isCurrentAbShared) | 
					
						
							|  |  |  |                   Align( | 
					
						
							|  |  |  |                     alignment: Alignment.centerLeft, | 
					
						
							|  |  |  |                     child: Text( | 
					
						
							|  |  |  |                       translate('Password'), | 
					
						
							|  |  |  |                       style: style, | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                   ).marginOnly(top: 8, bottom: marginBottom), | 
					
						
							|  |  |  |                 if (isCurrentAbShared) | 
					
						
							|  |  |  |                   TextField( | 
					
						
							|  |  |  |                     controller: passwordController, | 
					
						
							|  |  |  |                     obscureText: true, | 
					
						
							|  |  |  |                   ), | 
					
						
							| 
									
										
										
										
											2022-11-27 12:16:45 +08:00
										 |  |  |                 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, | 
					
						
							|  |  |  |             ), | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |             if (!gFFI.abModel.current.isPersonal()) | 
					
						
							|  |  |  |               Row(children: [ | 
					
						
							|  |  |  |                 Icon(Icons.info, color: Colors.amber).marginOnly(right: 4), | 
					
						
							|  |  |  |                 Text( | 
					
						
							|  |  |  |                   translate('share_warning_tip'), | 
					
						
							|  |  |  |                   style: TextStyle(fontSize: 12), | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |               ]).marginSymmetric(vertical: 10), | 
					
						
							| 
									
										
										
										
											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(','); | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |           gFFI.abModel.addTags(tags); | 
					
						
							| 
									
										
										
										
											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) | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |                     ? gFFI.abModel.getCurrentAbTagColor(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 | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |                             : gFFI.abModel.getCurrentAbTagColor(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'); | 
					
						
							|  |  |  |               } | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |               if (newName != name && | 
					
						
							|  |  |  |                   gFFI.abModel.currentAbTags.contains(newName)) { | 
					
						
							| 
									
										
										
										
											2023-08-10 10:08:33 +08:00
										 |  |  |                 return translate('Already exists'); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |               return null; | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             onSubmit: (String newName) { | 
					
						
							|  |  |  |               if (name != newName) { | 
					
						
							|  |  |  |                 gFFI.abModel.renameTag(name, newName); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |               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; | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |         Color oldColor = model.getCurrentAbTagColor(name); | 
					
						
							| 
									
										
										
										
											2023-08-22 19:07:01 +08:00
										 |  |  |         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); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }), | 
					
						
							| 
									
										
										
										
											2022-10-08 19:52:02 +09:00
										 |  |  |       getEntry(translate("Delete"), () { | 
					
						
							|  |  |  |         gFFI.abModel.deleteTag(name); | 
					
						
							|  |  |  |         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
										 |  |  | } |