123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864 |
- import 'dart:math';
- import 'package:dropdown_button2/dropdown_button2.dart';
- import 'package:dynamic_layouts/dynamic_layouts.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter_hbb/common/formatter/id_formatter.dart';
- import 'package:flutter_hbb/common/hbbs/hbbs.dart';
- import 'package:flutter_hbb/common/widgets/peer_card.dart';
- import 'package:flutter_hbb/common/widgets/peers_view.dart';
- import 'package:flutter_hbb/consts.dart';
- import 'package:flutter_hbb/desktop/widgets/popup_menu.dart';
- import 'package:flutter_hbb/models/ab_model.dart';
- import 'package:flutter_hbb/models/platform_model.dart';
- import 'package:flutter_hbb/models/state_model.dart';
- import 'package:url_launcher/url_launcher_string.dart';
- import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
- import 'package:get/get.dart';
- import 'package:flex_color_picker/flex_color_picker.dart';
- import '../../common.dart';
- import 'dialog.dart';
- import 'login.dart';
- final hideAbTagsPanel = false.obs;
- class AddressBook extends StatefulWidget {
- final EdgeInsets? menuPadding;
- const AddressBook({Key? key, this.menuPadding}) : super(key: key);
- @override
- State<StatefulWidget> createState() {
- return _AddressBookState();
- }
- }
- class _AddressBookState extends State<AddressBook> {
- var menuPos = RelativeRect.fill;
- @override
- Widget build(BuildContext context) => Obx(() {
- if (!gFFI.userModel.isLogin) {
- return Center(
- child: ElevatedButton(
- onPressed: loginDialog, child: Text(translate("Login"))));
- } else if (gFFI.userModel.networkError.isNotEmpty) {
- return netWorkErrorWidget();
- } else {
- return Column(
- children: [
- // NOT use Offstage to wrap LinearProgressIndicator
- if (gFFI.abModel.currentAbLoading.value &&
- gFFI.abModel.currentAbEmpty)
- const LinearProgressIndicator(),
- buildErrorBanner(context,
- loading: gFFI.abModel.currentAbLoading,
- err: gFFI.abModel.currentAbPullError,
- retry: null,
- close: () => gFFI.abModel.currentAbPullError.value = ''),
- buildErrorBanner(context,
- loading: gFFI.abModel.currentAbLoading,
- err: gFFI.abModel.currentAbPushError,
- retry: null, // remove retry
- close: () => gFFI.abModel.currentAbPushError.value = ''),
- Expanded(
- child: Obx(() => stateGlobal.isPortrait.isTrue
- ? _buildAddressBookPortrait()
- : _buildAddressBookLandscape()),
- ),
- ],
- );
- }
- });
- Widget _buildAddressBookLandscape() {
- return Row(
- children: [
- Offstage(
- offstage: hideAbTagsPanel.value,
- child: Container(
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(12),
- border: Border.all(
- color: Theme.of(context).colorScheme.background)),
- child: Container(
- width: 200,
- height: double.infinity,
- child: Column(
- children: [
- _buildAbDropdown(),
- _buildTagHeader().marginOnly(
- left: 8.0,
- right: gFFI.abModel.legacyMode.value ? 8.0 : 0,
- top: gFFI.abModel.legacyMode.value ? 8.0 : 0),
- Expanded(
- child: Container(
- width: double.infinity,
- height: double.infinity,
- child: _buildTags(),
- ),
- ),
- _buildAbPermission(),
- ],
- ),
- ),
- ).marginOnly(right: 12.0)),
- _buildPeersViews()
- ],
- );
- }
- Widget _buildAddressBookPortrait() {
- const padding = 8.0;
- return Column(
- children: [
- 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.fromLTRB(padding, 0, padding, padding),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- _buildAbDropdown(),
- _buildTagHeader().marginOnly(left: 8.0, right: 0),
- Container(
- width: double.infinity,
- child: _buildTags(),
- ),
- ],
- ),
- ),
- ).marginOnly(bottom: 12.0)),
- _buildPeersViews()
- ],
- );
- }
- 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();
- }
- // order: personal, divider, character order
- // https://pub.dev/packages/dropdown_button2#3-dropdownbutton2-with-items-of-different-heights-like-dividers
- final personalAddressBookName = gFFI.abModel.personalAddressBookName();
- bool contains = names.remove(personalAddressBookName);
- names.sort((a, b) => a.toLowerCase().compareTo(b.toLowerCase()));
- if (contains) {
- names.insert(0, personalAddressBookName);
- }
- Row buildItem(String e, {bool button = false}) {
- return Row(
- children: [
- Expanded(
- child: Tooltip(
- waitDuration: Duration(milliseconds: 500),
- message: gFFI.abModel.translatedName(e),
- child: Text(
- gFFI.abModel.translatedName(e),
- style: button ? null : TextStyle(fontSize: 14.0),
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- textAlign: button ? TextAlign.center : null,
- )),
- ),
- ],
- );
- }
- final items = names
- .map((e) => DropdownMenuItem(value: e, child: buildItem(e)))
- .toList();
- var menuItemStyleData = MenuItemStyleData(height: 36);
- if (contains && items.length > 1) {
- items.insert(1, DropdownMenuItem(enabled: false, child: Divider()));
- List<double> customHeights = List.filled(items.length, 36);
- customHeights[1] = 4;
- menuItemStyleData = MenuItemStyleData(customHeights: customHeights);
- }
- final TextEditingController textEditingController = TextEditingController();
- final isOptFixed = isOptionFixed(kOptionCurrentAbName);
- return DropdownButton2<String>(
- value: gFFI.abModel.currentName.value,
- onChanged: isOptFixed
- ? null
- : (value) {
- if (value != null) {
- gFFI.abModel.setCurrentName(value);
- bind.setLocalFlutterOption(k: kOptionCurrentAbName, v: value);
- }
- },
- customButton: Obx(() => Container(
- height: stateGlobal.isPortrait.isFalse ? 48 : 40,
- child: Row(children: [
- Expanded(
- child:
- buildItem(gFFI.abModel.currentName.value, button: true)),
- Icon(Icons.arrow_drop_down),
- ]),
- )),
- underline: Container(
- height: 0.7,
- color: Theme.of(context).dividerColor.withOpacity(0.1),
- ),
- menuItemStyleData: menuItemStyleData,
- items: items,
- isExpanded: true,
- isDense: 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());
- },
- ),
- );
- }
- Widget _buildTagHeader() {
- return Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text(translate('Tags')),
- Listener(
- onPointerDown: (e) {
- final x = e.position.dx;
- final y = e.position.dy;
- menuPos = RelativeRect.fromLTRB(x, y, x, y);
- },
- onPointerUp: (_) => _showMenu(menuPos),
- child: build_more(context, invert: true)),
- ],
- );
- }
- Widget _buildTags() {
- return Obx(() {
- final List tags;
- if (gFFI.abModel.sortTags.value) {
- tags = gFFI.abModel.currentAbTags.toList();
- tags.sort();
- } else {
- tags = gFFI.abModel.currentAbTags;
- }
- final editPermission = gFFI.abModel.current.canWrite();
- 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);
- }
- },
- showActionMenu: editPermission);
- }
- gridView(bool isPortrait) => DynamicGridView.builder(
- shrinkWrap: isPortrait,
- 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);
- return Obx(() => stateGlobal.isPortrait.isFalse
- ? gridView(false)
- : LimitedBox(maxHeight: maxHeight, child: gridView(true)));
- });
- }
- Widget _buildPeersViews() {
- return Expanded(
- child: Align(
- alignment: Alignment.topLeft,
- child: AddressBookPeersView(
- menuPadding: widget.menuPadding,
- )),
- );
- }
- @protected
- MenuEntryBase<String> syncMenuItem() {
- final isOptFixed = isOptionFixed(syncAbOption);
- return MenuEntrySwitch<String>(
- switchType: SwitchType.scheckbox,
- text: translate('Sync with recent sessions'),
- getter: () async {
- return shouldSyncAb();
- },
- setter: (bool v) async {
- gFFI.abModel.setShouldAsync(v);
- },
- dismissOnClicked: true,
- enabled: (!isOptFixed).obs,
- );
- }
- @protected
- MenuEntryBase<String> sortMenuItem() {
- final isOptFixed = isOptionFixed(sortAbTagsOption);
- 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' : defaultOptionNo);
- gFFI.abModel.sortTags.value = v;
- },
- dismissOnClicked: true,
- enabled: (!isOptFixed).obs,
- );
- }
- @protected
- MenuEntryBase<String> filterMenuItem() {
- final isOptFixed = isOptionFixed(filterAbTagOption);
- 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' : defaultOptionNo);
- gFFI.abModel.filterByIntersection.value = v;
- },
- dismissOnClicked: true,
- enabled: (!isOptFixed).obs,
- );
- }
- void _showMenu(RelativeRect pos) {
- final canWrite = gFFI.abModel.current.canWrite();
- final items = [
- if (canWrite) getEntry(translate("Add ID"), addIdToCurrentAb),
- if (canWrite) getEntry(translate("Add Tag"), abAddTag),
- getEntry(translate("Unselect all tags"), gFFI.abModel.unsetSelectedTags),
- if (gFFI.abModel.legacyMode.value)
- sortMenuItem(), // It's already sorted after pulling down
- if (canWrite) syncMenuItem(),
- filterMenuItem(),
- if (!gFFI.abModel.legacyMode.value && canWrite)
- MenuEntryDivider<String>(),
- if (!gFFI.abModel.legacyMode.value && canWrite)
- getEntry(translate("ab_web_console_tip"), () async {
- final url = await bind.mainGetApiServer();
- if (await canLaunchUrlString(url)) {
- launchUrlString(url);
- }
- }),
- ];
- 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,
- );
- }
- void addIdToCurrentAb() async {
- if (gFFI.abModel.isCurrentAbFull(true)) {
- return;
- }
- var isInProgress = false;
- var passwordVisible = false;
- IDTextEditingController idController = IDTextEditingController(text: '');
- TextEditingController aliasController = TextEditingController(text: '');
- TextEditingController passwordController = TextEditingController(text: '');
- final tags = List.of(gFFI.abModel.currentAbTags);
- var selectedTag = List<dynamic>.empty(growable: true).obs;
- final style = TextStyle(fontSize: 14.0);
- String? errorMsg;
- final isCurrentAbShared = !gFFI.abModel.current.isPersonal();
- gFFI.dialogManager.show((setState, close, context) {
- submit() async {
- setState(() {
- isInProgress = true;
- errorMsg = null;
- });
- String id = idController.id;
- if (id.isEmpty) {
- // pass
- } else {
- if (gFFI.abModel.idContainByCurrent(id)) {
- setState(() {
- isInProgress = false;
- errorMsg = translate('ID already exists');
- });
- return;
- }
- 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;
- }
- // final currentPeers
- }
- close();
- }
- double marginBottom = 4;
- row({required Widget lable, required Widget input}) {
- makeChild(bool isPortrait) => Row(
- children: [
- !isPortrait
- ? ConstrainedBox(
- constraints: const BoxConstraints(minWidth: 100),
- child: lable.marginOnly(right: 10))
- : SizedBox.shrink(),
- Expanded(
- child: ConstrainedBox(
- constraints: const BoxConstraints(minWidth: 200),
- child: input),
- ),
- ],
- ).marginOnly(bottom: !isPortrait ? 8 : 0);
- return Obx(() => makeChild(stateGlobal.isPortrait.isTrue));
- }
- return CustomAlertDialog(
- title: Text(translate("Add ID")),
- content: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Column(
- children: [
- row(
- lable: Row(
- children: [
- Text(
- '*',
- style: TextStyle(color: Colors.red, fontSize: 14),
- ),
- Text(
- 'ID',
- style: style,
- ),
- ],
- ),
- input: Obx(() => TextField(
- controller: idController,
- inputFormatters: [IDTextInputFormatter()],
- decoration: InputDecoration(
- labelText: stateGlobal.isPortrait.isFalse
- ? null
- : translate('ID'),
- errorText: errorMsg,
- errorMaxLines: 5),
- ))),
- row(
- lable: Text(
- translate('Alias'),
- style: style,
- ),
- input: Obx(() => TextField(
- controller: aliasController,
- decoration: InputDecoration(
- labelText: stateGlobal.isPortrait.isFalse
- ? null
- : translate('Alias'),
- ),
- )),
- ),
- if (isCurrentAbShared)
- row(
- lable: Text(
- translate('Password'),
- style: style,
- ),
- input: Obx(
- () => TextField(
- controller: passwordController,
- obscureText: !passwordVisible,
- decoration: InputDecoration(
- labelText: stateGlobal.isPortrait.isFalse
- ? null
- : translate('Password'),
- suffixIcon: IconButton(
- icon: Icon(
- passwordVisible
- ? Icons.visibility
- : Icons.visibility_off,
- color: MyTheme.lightTheme.primaryColor),
- onPressed: () {
- setState(() {
- passwordVisible = !passwordVisible;
- });
- },
- ),
- ),
- ),
- )),
- if (gFFI.abModel.currentAbTags.isNotEmpty)
- Align(
- alignment: Alignment.centerLeft,
- child: Text(
- translate('Tags'),
- style: style,
- ),
- ).marginOnly(top: 8, bottom: marginBottom),
- if (gFFI.abModel.currentAbTags.isNotEmpty)
- Align(
- alignment: Alignment.centerLeft,
- 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),
- ),
- ),
- ],
- ),
- const SizedBox(
- height: 4.0,
- ),
- 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),
- // NOT use Offstage to wrap LinearProgressIndicator
- if (isInProgress) const LinearProgressIndicator(),
- ],
- ),
- actions: [
- dialogButton("Cancel", onPressed: close, isOutline: true),
- dialogButton("OK", onPressed: submit),
- ],
- onSubmit: submit,
- onCancel: close,
- );
- });
- }
- void abAddTag() async {
- var field = "";
- var msg = "";
- var isInProgress = false;
- TextEditingController controller = TextEditingController(text: field);
- gFFI.dialogManager.show((setState, close, context) {
- 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(',');
- gFFI.abModel.addTags(tags);
- // 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,
- autofocus: true,
- ),
- ),
- ],
- ),
- const SizedBox(
- height: 4.0,
- ),
- // NOT use Offstage to wrap LinearProgressIndicator
- if (isInProgress) const LinearProgressIndicator(),
- ],
- ),
- actions: [
- dialogButton("Cancel", onPressed: close, isOutline: true),
- dialogButton("OK", onPressed: submit),
- ],
- onSubmit: submit,
- onCancel: close,
- );
- });
- }
- }
- class AddressBookTag extends StatelessWidget {
- final String name;
- final RxList<dynamic> tags;
- final Function()? onTap;
- final bool showActionMenu;
- const AddressBookTag(
- {Key? key,
- required this.name,
- required this.tags,
- this.onTap,
- this.showActionMenu = true})
- : super(key: key);
- @override
- Widget build(BuildContext context) {
- 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);
- }
- const double radius = 8;
- return GestureDetector(
- onTap: onTap,
- onTapDown: showActionMenu ? setPosition : null,
- onSecondaryTapDown: showActionMenu ? setPosition : null,
- onSecondaryTap: showActionMenu ? () => _showMenu(context, pos) : null,
- onLongPress: showActionMenu ? () => _showMenu(context, pos) : null,
- child: Obx(() => Container(
- decoration: BoxDecoration(
- color: tags.contains(name)
- ? gFFI.abModel.getCurrentAbTagColor(name)
- : 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
- : gFFI.abModel.getCurrentAbTagColor(name)),
- ).marginOnly(right: radius / 2),
- Expanded(
- child: Text(name,
- style: TextStyle(
- overflow: TextOverflow.ellipsis,
- color: tags.contains(name) ? Colors.white : null)),
- ),
- ],
- ),
- ),
- )),
- );
- }
- void _showMenu(BuildContext context, RelativeRect pos) {
- final items = [
- 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.currentAbTags.contains(newName)) {
- 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());
- });
- }),
- getEntry(translate(translate('Change Color')), () async {
- final model = gFFI.abModel;
- Color oldColor = model.getCurrentAbTagColor(name);
- Color newColor = await showColorPickerDialog(
- context,
- oldColor,
- pickersEnabled: {
- ColorPickerType.accent: false,
- ColorPickerType.wheel: true,
- },
- pickerTypeLabels: {
- ColorPickerType.primary: translate("Primary Color"),
- ColorPickerType.wheel: translate("HSV Color"),
- },
- actionButtons: ColorPickerActionButtons(
- dialogOkButtonLabel: translate("OK"),
- dialogCancelButtonLabel: translate("Cancel")),
- showColorCode: true,
- );
- if (oldColor != newColor) {
- model.setTagColor(name, newColor);
- }
- }),
- 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,
- );
- }
|