From ff9c3bccb5a20d2c2f69ae55922b776e53eac6fd Mon Sep 17 00:00:00 2001 From: csf Date: Fri, 30 Sep 2022 16:15:21 +0800 Subject: [PATCH 1/8] remove comma when self-host server --- .../lib/desktop/pages/connection_page.dart | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index 627cd23e9..480fd1518 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -288,17 +288,23 @@ class _ConnectionPageState extends State children: [ light, Text(translate('Ready'), style: textStyle), - Text(', ', style: textStyle), - svcIsUsingPublicServer.value - ? InkWell( - onTap: onUsePublicServerGuide, - child: Text( - translate('setup_server_tip'), - style: TextStyle( - decoration: TextDecoration.underline, fontSize: fontSize), - ), - ) - : Offstage() + Offstage( + offstage: !svcIsUsingPublicServer.value, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text(', ', style: textStyle), + InkWell( + onTap: onUsePublicServerGuide, + child: Text( + translate('setup_server_tip'), + style: TextStyle( + decoration: TextDecoration.underline, + fontSize: fontSize), + ), + ) + ], + )) ], ); } From 9449e50ba48dea103135201b3e52ccbe8e24f961 Mon Sep 17 00:00:00 2001 From: csf Date: Sat, 8 Oct 2022 10:55:54 +0900 Subject: [PATCH 2/8] fix ab tag selection state and opt ab style --- flutter/lib/common/widgets/address_book.dart | 9 ++-- flutter/lib/common/widgets/peers_view.dart | 55 +++++++------------- 2 files changed, 25 insertions(+), 39 deletions(-) diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index 47a992bd3..357294896 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -113,8 +113,9 @@ class _AddressBookState extends State { builder: (context, model, child) => Row( children: [ Card( + margin: EdgeInsets.symmetric(horizontal: 4.0), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), + borderRadius: BorderRadius.circular(12), side: BorderSide( color: Theme.of(context).scaffoldBackgroundColor)), child: Container( @@ -127,6 +128,7 @@ class _AddressBookState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + // TODO same style as peer Text(translate('Tags')), InkWell( child: PopupMenuButton( @@ -155,7 +157,8 @@ class _AddressBookState extends State { width: double.infinity, height: double.infinity, decoration: BoxDecoration( - border: Border.all(color: MyTheme.darkGray)), + border: Border.all(color: MyTheme.darkGray), + borderRadius: BorderRadius.circular(2)), child: Obx( () => Wrap( children: gFFI.abModel.tags @@ -210,7 +213,7 @@ class _AddressBookState extends State { decoration: BoxDecoration( color: rxTags.contains(tagName) ? Colors.blue : null, border: Border.all(color: MyTheme.darkGray), - borderRadius: BorderRadius.circular(10)), + borderRadius: BorderRadius.circular(6)), margin: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 8.0), padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 8.0), child: Text( diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index 9316c792b..9e9129486 100644 --- a/flutter/lib/common/widgets/peers_view.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:visibility_detector/visibility_detector.dart'; @@ -14,8 +13,7 @@ import '../../models/peer_model.dart'; import '../../models/platform_model.dart'; import 'peer_card.dart'; -typedef OffstageFunc = bool Function(Peer peer); -typedef PeerCardBuilder = BasePeerCard Function(Peer peer); +typedef PeerCardBuilder = Widget Function(Peer peer); /// for peer search text, global obs value final peerSearchText = "".obs; @@ -24,16 +22,10 @@ final peerSearchTextController = class _PeersView extends StatefulWidget { final Peers peers; - final OffstageFunc offstageFunc; final PeerCardBuilder peerCardBuilder; - final ScrollController? scrollController; const _PeersView( - {required this.peers, - required this.offstageFunc, - required this.peerCardBuilder, - Key? key, - this.scrollController}) + {required this.peers, required this.peerCardBuilder, Key? key}) : super(key: key); @override @@ -124,20 +116,16 @@ class _PeersViewState extends State<_PeersView> with WindowListener { }, child: widget.peerCardBuilder(peer), ); - cards.add(Offstage( - key: ValueKey("off${peer.id}"), - offstage: widget.offstageFunc(peer), - child: isDesktop - ? Obx( - () => SizedBox( - width: 220, - height: peerCardUiType.value == PeerUiType.grid - ? 140 - : 42, - child: visibilityChild, - ), - ) - : SizedBox(width: mobileWidth, child: visibilityChild))); + cards.add(isDesktop + ? Obx( + () => SizedBox( + width: 220, + height: + peerCardUiType.value == PeerUiType.grid ? 140 : 42, + child: visibilityChild, + ), + ) + : SizedBox(width: mobileWidth, child: visibilityChild)); } return Wrap(spacing: space, runSpacing: space, children: cards); } else { @@ -190,7 +178,6 @@ class _PeersViewState extends State<_PeersView> with WindowListener { abstract class BasePeersView extends StatelessWidget { final String name; final String loadEvent; - final OffstageFunc offstageFunc; final PeerCardBuilder peerCardBuilder; final List initPeers; @@ -198,7 +185,6 @@ abstract class BasePeersView extends StatelessWidget { Key? key, required this.name, required this.loadEvent, - required this.offstageFunc, required this.peerCardBuilder, required this.initPeers, }) : super(key: key); @@ -207,7 +193,6 @@ abstract class BasePeersView extends StatelessWidget { Widget build(BuildContext context) { return _PeersView( peers: Peers(name: name, loadEvent: loadEvent, peers: initPeers), - offstageFunc: offstageFunc, peerCardBuilder: peerCardBuilder); } } @@ -219,7 +204,6 @@ class RecentPeersView extends BasePeersView { key: key, name: 'recent peer', loadEvent: 'load_recent_peers', - offstageFunc: (Peer peer) => false, peerCardBuilder: (Peer peer) => RecentPeerCard( peer: peer, menuPadding: menuPadding, @@ -242,7 +226,6 @@ class FavoritePeersView extends BasePeersView { key: key, name: 'favorite peer', loadEvent: 'load_fav_peers', - offstageFunc: (Peer peer) => false, peerCardBuilder: (Peer peer) => FavoritePeerCard( peer: peer, menuPadding: menuPadding, @@ -265,7 +248,6 @@ class DiscoveredPeersView extends BasePeersView { key: key, name: 'discovered peer', loadEvent: 'load_lan_peers', - offstageFunc: (Peer peer) => false, peerCardBuilder: (Peer peer) => DiscoveredPeerCard( peer: peer, menuPadding: menuPadding, @@ -288,12 +270,13 @@ class AddressBookPeersView extends BasePeersView { key: key, name: 'address book peer', loadEvent: 'load_address_book_peers', - offstageFunc: (Peer peer) => - !_hitTag(gFFI.abModel.selectedTags, peer.tags), - peerCardBuilder: (Peer peer) => AddressBookPeerCard( - peer: peer, - menuPadding: menuPadding, - ), + peerCardBuilder: (Peer peer) => Obx(() => Offstage( + key: ValueKey("off${peer.id}"), + offstage: !_hitTag(gFFI.abModel.selectedTags, peer.tags), + child: AddressBookPeerCard( + peer: peer, + menuPadding: menuPadding, + ))), initPeers: _loadPeers(), ); From ee1a7fb7919cefb846fd5a69078f69ca305761a4 Mon Sep 17 00:00:00 2001 From: csf Date: Sat, 8 Oct 2022 16:53:03 +0900 Subject: [PATCH 3/8] fix ab peers state --- flutter/lib/common/widgets/address_book.dart | 7 +-- flutter/lib/common/widgets/peer_card.dart | 6 +-- flutter/lib/common/widgets/peers_view.dart | 14 ++---- flutter/lib/models/ab_model.dart | 48 ++++++++++---------- flutter/lib/models/peer_model.dart | 15 +++++- 5 files changed, 50 insertions(+), 40 deletions(-) diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index 357294896..b2339e850 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -185,9 +185,10 @@ class _AddressBookState extends State { Expanded( child: Align( alignment: Alignment.topLeft, - child: AddressBookPeersView( - menuPadding: widget.menuPadding, - )), + child: Obx(() => AddressBookPeersView( + menuPadding: widget.menuPadding, + initPeers: gFFI.abModel.peers.value, + ))), ) ], )); diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index e3ab82cf5..5d5eb998a 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -601,11 +601,11 @@ abstract class BasePeerCard extends StatelessWidget { var name = peer.alias; var controller = TextEditingController(text: name); if (isAddressBook) { - final peer = gFFI.abModel.peers.firstWhere((p) => id == p['id']); + final peer = gFFI.abModel.peers.firstWhereOrNull((p) => id == p.id); if (peer == null) { // this should not happen } else { - name = peer['alias'] ?? ''; + name = peer.alias; } } gFFI.dialogManager.show((setState, close) { @@ -614,7 +614,7 @@ abstract class BasePeerCard extends StatelessWidget { name = controller.text; await bind.mainSetPeerOption(id: id, key: 'alias', value: name); if (isAddressBook) { - gFFI.abModel.setPeerOption(id, 'alias', name); + gFFI.abModel.setPeerAlias(id, name); await gFFI.abModel.updateAb(); } if (isAddressBook) { diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index 9e9129486..80e2c089b 100644 --- a/flutter/lib/common/widgets/peers_view.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -265,7 +265,10 @@ class DiscoveredPeersView extends BasePeersView { class AddressBookPeersView extends BasePeersView { AddressBookPeersView( - {Key? key, EdgeInsets? menuPadding, ScrollController? scrollController}) + {Key? key, + EdgeInsets? menuPadding, + ScrollController? scrollController, + required List initPeers}) : super( key: key, name: 'address book peer', @@ -277,16 +280,9 @@ class AddressBookPeersView extends BasePeersView { peer: peer, menuPadding: menuPadding, ))), - initPeers: _loadPeers(), + initPeers: initPeers, ); - static List _loadPeers() { - debugPrint("_loadPeers : ${gFFI.abModel.peers.toString()}"); - return gFFI.abModel.peers.map((e) { - return Peer.fromJson(e); - }).toList(); - } - static bool _hitTag(List selectedTags, List idents) { if (selectedTags.isEmpty) { return true; diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index b4de861e9..14d67eb68 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/models/model.dart'; +import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; @@ -12,7 +13,7 @@ class AbModel with ChangeNotifier { var abLoading = false; var abError = ""; var tags = [].obs; - var peers = [].obs; + var peers = List.empty(growable: true).obs; var selectedTags = List.empty(growable: true).obs; @@ -26,7 +27,7 @@ class AbModel with ChangeNotifier { abLoading = true; notifyListeners(); // request - final api = "${await getApiServer()}/api/ab/get"; + final api = "${await bind.mainGetApiServer()}/api/ab/get"; try { final resp = await http.post(Uri.parse(api), headers: await getHttpHeaders()); @@ -37,7 +38,10 @@ class AbModel with ChangeNotifier { } else if (json.containsKey('data')) { final data = jsonDecode(json['data']); tags.value = data['tags']; - peers.value = data['peers']; + peers.clear(); + for (final peer in data['peers']) { + peers.add(Peer.fromJson(peer)); + } } notifyListeners(); return resp.body; @@ -45,6 +49,7 @@ class AbModel with ChangeNotifier { return ""; } } catch (err) { + err.printError(); abError = err.toString(); } finally { abLoading = false; @@ -53,10 +58,6 @@ class AbModel with ChangeNotifier { return null; } - Future getApiServer() async { - return await bind.mainGetApiServer(); - } - void reset() { tags.clear(); peers.clear(); @@ -67,7 +68,7 @@ class AbModel with ChangeNotifier { if (idContainBy(id)) { return; } - peers.add({"id": id}); + peers.add(Peer.fromJson({"id": id})); notifyListeners(); } @@ -80,21 +81,22 @@ class AbModel with ChangeNotifier { } void changeTagForPeer(String id, List tags) { - final it = peers.where((element) => element['id'] == id); + final it = peers.where((element) => element.id == id); if (it.isEmpty) { return; } - it.first['tags'] = tags; + it.first.tags = tags; } Future updateAb() async { abLoading = true; notifyListeners(); - final api = "${await getApiServer()}/api/ab"; + final api = "${await bind.mainGetApiServer()}/api/ab"; var authHeaders = await getHttpHeaders(); authHeaders['Content-Type'] = "application/json"; + final peersJsonData = peers.map((e) => e.toJson()).toList(); final body = jsonEncode({ - "data": jsonEncode({"tags": tags, "peers": peers}) + "data": jsonEncode({"tags": tags, "peers": peersJsonData}) }); try { final resp = @@ -111,7 +113,7 @@ class AbModel with ChangeNotifier { } bool idContainBy(String id) { - return peers.where((element) => element['id'] == id).isNotEmpty; + return peers.where((element) => element.id == id).isNotEmpty; } bool tagContainBy(String tag) { @@ -119,18 +121,18 @@ class AbModel with ChangeNotifier { } void deletePeer(String id) { - peers.removeWhere((element) => element['id'] == id); + peers.removeWhere((element) => element.id == id); notifyListeners(); } void deleteTag(String tag) { tags.removeWhere((element) => element == tag); for (var peer in peers) { - if (peer['tags'] == null) { + if (peer.tags.isEmpty) { continue; } - if (((peer['tags']) as List).contains(tag)) { - ((peer['tags']) as List).remove(tag); + if (peer.tags.contains(tag)) { + ((peer.tags)).remove(tag); } } notifyListeners(); @@ -142,21 +144,21 @@ class AbModel with ChangeNotifier { } List getPeerTags(String id) { - final it = peers.where((p0) => p0['id'] == id); + final it = peers.where((p0) => p0.id == id); if (it.isEmpty) { return []; } else { - return it.first['tags'] ?? []; + return it.first.tags; } } - void setPeerOption(String id, String key, String value) { - final it = peers.where((p0) => p0['id'] == id); + void setPeerAlias(String id, String value) { + final it = peers.where((p0) => p0.id == id); if (it.isEmpty) { - debugPrint("${id} is not exists"); + debugPrint("$id is not exists"); return; } else { - it.first[key] = value; + it.first.alias = value; } } diff --git a/flutter/lib/models/peer_model.dart b/flutter/lib/models/peer_model.dart index c68ca26df..6dd94bcf4 100644 --- a/flutter/lib/models/peer_model.dart +++ b/flutter/lib/models/peer_model.dart @@ -7,8 +7,8 @@ class Peer { final String username; final String hostname; final String platform; - final String alias; - final List tags; + String alias; + List tags; bool online = false; Peer.fromJson(Map json) @@ -19,6 +19,17 @@ class Peer { alias = json['alias'] ?? '', tags = json['tags'] ?? []; + Map toJson() { + return { + "id": id, + "username": username, + "hostname": hostname, + "platform": platform, + "alias": alias, + "tags": tags, + }; + } + Peer({ required this.id, required this.username, From a13c4c590768b5b72635d1001938a4f5818d3816 Mon Sep 17 00:00:00 2001 From: csf Date: Sat, 8 Oct 2022 17:13:24 +0900 Subject: [PATCH 4/8] AbModel use getx only --- flutter/lib/common/widgets/address_book.dart | 227 +++++++++--------- flutter/lib/common/widgets/peer_card.dart | 10 +- flutter/lib/common/widgets/peer_tab_page.dart | 2 +- flutter/lib/common/widgets/peers_view.dart | 7 - flutter/lib/main.dart | 1 - flutter/lib/models/ab_model.dart | 38 +-- 6 files changed, 127 insertions(+), 158 deletions(-) diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index b2339e850..081135ef2 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -1,9 +1,7 @@ import 'package:contextmenu/contextmenu.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/widgets/peers_view.dart'; -import 'package:flutter_hbb/models/ab_model.dart'; import 'package:get/get.dart'; -import 'package:provider/provider.dart'; import '../../common.dart'; import '../../desktop/pages/desktop_home_page.dart'; @@ -24,7 +22,7 @@ class _AddressBookState extends State { @override void initState() { super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) => gFFI.abModel.getAb()); + WidgetsBinding.instance.addPostFrameCallback((_) => gFFI.abModel.pullAb()); } @override @@ -66,132 +64,123 @@ class _AddressBookState extends State { } final model = gFFI.abModel; return FutureBuilder( - future: model.getAb(), + future: model.pullAb(), builder: (context, snapshot) { if (snapshot.hasData) { return _buildAddressBook(context); } else if (snapshot.hasError) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(translate("${snapshot.error}")), - TextButton( - onPressed: () { - setState(() {}); - }, - child: Text(translate("Retry"))) - ], - ); + return _buildShowError(snapshot.error.toString()); } else { - if (model.abLoading) { - return const Center( - child: CircularProgressIndicator(), - ); - } else if (model.abError.isNotEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(translate(model.abError)), - TextButton( - onPressed: () { - setState(() {}); - }, - child: Text(translate("Retry"))) - ], - ), - ); - } else { - return const Offstage(); - } + return Obx(() { + if (model.abLoading.value) { + return const Center( + child: CircularProgressIndicator(), + ); + } else if (model.abError.isNotEmpty) { + return _buildShowError(model.abError.value); + } else { + return const Offstage(); + } + }); } }); } + Widget _buildShowError(String error) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(translate(error)), + TextButton( + onPressed: () { + setState(() {}); + }, + child: Text(translate("Retry"))) + ], + )); + } + Widget _buildAddressBook(BuildContext context) { - return Consumer( - builder: (context, model, child) => Row( + return Row( + children: [ + Card( + margin: EdgeInsets.symmetric(horizontal: 4.0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: + BorderSide(color: Theme.of(context).scaffoldBackgroundColor)), + child: Container( + width: 200, + height: double.infinity, + padding: + const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), + child: Column( children: [ - Card( - margin: EdgeInsets.symmetric(horizontal: 4.0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - side: BorderSide( - color: Theme.of(context).scaffoldBackgroundColor)), - child: Container( - width: 200, - height: double.infinity, - padding: const EdgeInsets.symmetric( - horizontal: 12.0, vertical: 8.0), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // TODO same style as peer - Text(translate('Tags')), - InkWell( - child: PopupMenuButton( - itemBuilder: (context) => [ - PopupMenuItem( - value: 'add-id', - child: Text(translate("Add ID")), - ), - PopupMenuItem( - value: 'add-tag', - child: Text(translate("Add Tag")), - ), - PopupMenuItem( - value: 'unset-all-tag', - child: Text( - translate("Unselect all tags")), - ), - ], - onSelected: handleAbOp, - child: const Icon(Icons.more_vert_outlined)), - ) - ], - ), - Expanded( - child: Container( - width: double.infinity, - height: double.infinity, - decoration: BoxDecoration( - border: Border.all(color: MyTheme.darkGray), - borderRadius: BorderRadius.circular(2)), - child: Obx( - () => Wrap( - children: gFFI.abModel.tags - .map((e) => - buildTag(e, gFFI.abModel.selectedTags, - onTap: () { - // - if (gFFI.abModel.selectedTags - .contains(e)) { - gFFI.abModel.selectedTags.remove(e); - } else { - gFFI.abModel.selectedTags.add(e); - } - })) - .toList(), - ), - ), - ).marginSymmetric(vertical: 8.0), - ) - ], - ), - ), - ).marginOnly(right: 8.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // TODO same style as peer + Text(translate('Tags')), + InkWell( + child: PopupMenuButton( + itemBuilder: (context) => [ + PopupMenuItem( + value: 'add-id', + child: Text(translate("Add ID")), + ), + PopupMenuItem( + value: 'add-tag', + child: Text(translate("Add Tag")), + ), + PopupMenuItem( + value: 'unset-all-tag', + child: Text(translate("Unselect all tags")), + ), + ], + onSelected: handleAbOp, + child: const Icon(Icons.more_vert_outlined)), + ) + ], + ), Expanded( - child: Align( - alignment: Alignment.topLeft, - child: Obx(() => AddressBookPeersView( - menuPadding: widget.menuPadding, - initPeers: gFFI.abModel.peers.value, - ))), + child: Container( + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration( + border: Border.all(color: MyTheme.darkGray), + borderRadius: BorderRadius.circular(2)), + child: Obx( + () => Wrap( + children: gFFI.abModel.tags + .map((e) => buildTag(e, gFFI.abModel.selectedTags, + onTap: () { + // + if (gFFI.abModel.selectedTags.contains(e)) { + gFFI.abModel.selectedTags.remove(e); + } else { + gFFI.abModel.selectedTags.add(e); + } + })) + .toList(), + ), + ), + ).marginSymmetric(vertical: 8.0), ) ], - )); + ), + ), + ).marginOnly(right: 8.0), + Expanded( + child: Align( + alignment: Alignment.topLeft, + child: Obx(() => AddressBookPeersView( + menuPadding: widget.menuPadding, + initPeers: gFFI.abModel.peers.value, + ))), + ) + ], + ); } Widget buildTag(String tagName, RxList rxTags, {Function()? onTap}) { @@ -202,7 +191,7 @@ class _AddressBookState extends State { title: Text(translate("Delete")), onTap: () { gFFI.abModel.deleteTag(tagName); - gFFI.abModel.updateAb(); + gFFI.abModel.pushAb(); Future.delayed(Duration.zero, () => Get.back()); }, ) @@ -264,7 +253,7 @@ class _AddressBookState extends State { } gFFI.abModel.addId(newId); } - await gFFI.abModel.updateAb(); + await gFFI.abModel.pushAb(); this.setState(() {}); // final currentPeers } @@ -331,7 +320,7 @@ class _AddressBookState extends State { for (final tag in tags) { gFFI.abModel.addTag(tag); } - await gFFI.abModel.updateAb(); + await gFFI.abModel.pushAb(); // final currentPeers } close(); @@ -390,7 +379,7 @@ class _AddressBookState extends State { isInProgress = true; }); gFFI.abModel.changeTagForPeer(id, selectedTag); - await gFFI.abModel.updateAb(); + await gFFI.abModel.pushAb(); close(); } diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 5d5eb998a..d4a418262 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -615,10 +615,10 @@ abstract class BasePeerCard extends StatelessWidget { await bind.mainSetPeerOption(id: id, key: 'alias', value: name); if (isAddressBook) { gFFI.abModel.setPeerAlias(id, name); - await gFFI.abModel.updateAb(); + await gFFI.abModel.pushAb(); } if (isAddressBook) { - gFFI.abModel.getAb(); + gFFI.abModel.pullAb(); } else { bind.mainLoadRecentPeers(); bind.mainLoadFavPeers(); @@ -791,7 +791,7 @@ class AddressBookPeerCard extends BasePeerCard { proc: () { () async { gFFI.abModel.deletePeer(id); - await gFFI.abModel.updateAb(); + await gFFI.abModel.pushAb(); }(); }, padding: super.menuPadding, @@ -826,7 +826,7 @@ class AddressBookPeerCard extends BasePeerCard { isInProgress = true; }); gFFI.abModel.changeTagForPeer(id, selectedTag); - await gFFI.abModel.updateAb(); + await gFFI.abModel.pushAb(); close(); } @@ -873,7 +873,7 @@ class AddressBookPeerCard extends BasePeerCard { title: Text(translate("Delete")), onTap: () { gFFI.abModel.deleteTag(tagName); - gFFI.abModel.updateAb(); + gFFI.abModel.pushAb(); Future.delayed(Duration.zero, () => Get.back()); }, ) diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index c91b01ca8..9a5503e26 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -54,7 +54,7 @@ class _PeerTabPageState extends State bind.mainDiscover(); break; case 3: - gFFI.abModel.getAb(); + gFFI.abModel.pullAb(); break; } } diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index 80e2c089b..03a2436f2 100644 --- a/flutter/lib/common/widgets/peers_view.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -297,11 +297,4 @@ class AddressBookPeersView extends BasePeersView { } return true; } - - @override - Widget build(BuildContext context) { - final widget = super.build(context); - // gFFI.abModel.updateAb(); - return widget; - } } diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 9ba69a476..230199431 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -267,7 +267,6 @@ class _AppState extends State { ChangeNotifierProvider.value(value: gFFI.imageModel), ChangeNotifierProvider.value(value: gFFI.cursorModel), ChangeNotifierProvider.value(value: gFFI.canvasModel), - ChangeNotifierProvider.value(value: gFFI.abModel), ChangeNotifierProvider.value(value: gFFI.userModel), ], child: GetMaterialApp( diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index 14d67eb68..30da5e52d 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -9,9 +9,9 @@ import 'package:http/http.dart' as http; import '../common.dart'; -class AbModel with ChangeNotifier { - var abLoading = false; - var abError = ""; +class AbModel { + var abLoading = false.obs; + var abError = "".obs; var tags = [].obs; var peers = List.empty(growable: true).obs; @@ -23,9 +23,8 @@ class AbModel with ChangeNotifier { FFI? get _ffi => parent.target; - Future getAb() async { - abLoading = true; - notifyListeners(); + Future pullAb() async { + abLoading.value = true; // request final api = "${await bind.mainGetApiServer()}/api/ab/get"; try { @@ -43,17 +42,15 @@ class AbModel with ChangeNotifier { peers.add(Peer.fromJson(peer)); } } - notifyListeners(); return resp.body; } else { return ""; } } catch (err) { err.printError(); - abError = err.toString(); + abError.value = err.toString(); } finally { - abLoading = false; - notifyListeners(); + abLoading.value = false; } return null; } @@ -61,7 +58,6 @@ class AbModel with ChangeNotifier { void reset() { tags.clear(); peers.clear(); - notifyListeners(); } void addId(String id) async { @@ -69,7 +65,6 @@ class AbModel with ChangeNotifier { return; } peers.add(Peer.fromJson({"id": id})); - notifyListeners(); } void addTag(String tag) async { @@ -77,7 +72,6 @@ class AbModel with ChangeNotifier { return; } tags.add(tag); - notifyListeners(); } void changeTagForPeer(String id, List tags) { @@ -88,9 +82,8 @@ class AbModel with ChangeNotifier { it.first.tags = tags; } - Future updateAb() async { - abLoading = true; - notifyListeners(); + Future pushAb() async { + abLoading.value = true; final api = "${await bind.mainGetApiServer()}/api/ab"; var authHeaders = await getHttpHeaders(); authHeaders['Content-Type'] = "application/json"; @@ -101,15 +94,14 @@ class AbModel with ChangeNotifier { try { final resp = await http.post(Uri.parse(api), headers: authHeaders, body: body); - abError = ""; - await getAb(); + abError.value = ""; + await pullAb(); debugPrint("resp: ${resp.body}"); } catch (e) { - abError = e.toString(); + abError.value = e.toString(); } finally { - abLoading = false; + abLoading.value = false; } - notifyListeners(); } bool idContainBy(String id) { @@ -122,7 +114,6 @@ class AbModel with ChangeNotifier { void deletePeer(String id) { peers.removeWhere((element) => element.id == id); - notifyListeners(); } void deleteTag(String tag) { @@ -135,12 +126,10 @@ class AbModel with ChangeNotifier { ((peer.tags)).remove(tag); } } - notifyListeners(); } void unsetSelectedTags() { selectedTags.clear(); - notifyListeners(); } List getPeerTags(String id) { @@ -165,6 +154,5 @@ class AbModel with ChangeNotifier { void clear() { peers.clear(); tags.clear(); - notifyListeners(); } } From 6a922122167e8110e5142945828c32cd9ffb7aa3 Mon Sep 17 00:00:00 2001 From: csf Date: Sat, 8 Oct 2022 17:39:05 +0900 Subject: [PATCH 5/8] use shared AddressBookTag widget & hide _editTagAction when tags is empty --- flutter/lib/common/widgets/address_book.dart | 133 +++++++------------ flutter/lib/common/widgets/peer_card.dart | 52 ++------ 2 files changed, 61 insertions(+), 124 deletions(-) diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index 081135ef2..6bf6a76c4 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -153,9 +153,10 @@ class _AddressBookState extends State { child: Obx( () => Wrap( children: gFFI.abModel.tags - .map((e) => buildTag(e, gFFI.abModel.selectedTags, - onTap: () { - // + .map((e) => AddressBookTag( + name: e, + tags: gFFI.abModel.selectedTags, + onTap: () { if (gFFI.abModel.selectedTags.contains(e)) { gFFI.abModel.selectedTags.remove(e); } else { @@ -183,41 +184,6 @@ class _AddressBookState extends State { ); } - Widget buildTag(String tagName, RxList rxTags, {Function()? onTap}) { - return ContextMenuArea( - width: 100, - builder: (context) => [ - ListTile( - title: Text(translate("Delete")), - onTap: () { - gFFI.abModel.deleteTag(tagName); - gFFI.abModel.pushAb(); - Future.delayed(Duration.zero, () => Get.back()); - }, - ) - ], - child: GestureDetector( - onTap: onTap, - child: Obx( - () => Container( - decoration: BoxDecoration( - color: rxTags.contains(tagName) ? Colors.blue : null, - border: Border.all(color: MyTheme.darkGray), - borderRadius: BorderRadius.circular(6)), - margin: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 8.0), - padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 8.0), - child: Text( - tagName, - style: TextStyle( - color: - rxTags.contains(tagName) ? Colors.white : null), // TODO - ), - ), - ), - ), - ); - } - /// tag operation void handleAbOp(String value) { if (value == 'add-id') { @@ -366,54 +332,55 @@ class _AddressBookState extends State { ); }); } +} - void abEditTag(String id) { - var isInProgress = false; +class AddressBookTag extends StatelessWidget { + final String name; + final RxList tags; + final Function()? onTap; + final bool useContextMenuArea; - final tags = List.of(gFFI.abModel.tags); - var selectedTag = gFFI.abModel.getPeerTags(id).obs; + const AddressBookTag( + {Key? key, + required this.name, + required this.tags, + this.onTap, + this.useContextMenuArea = true}) + : super(key: key); - gFFI.dialogManager.show((setState, close) { - submit() async { - setState(() { - isInProgress = true; - }); - gFFI.abModel.changeTagForPeer(id, selectedTag); - await gFFI.abModel.pushAb(); - close(); - } - - return CustomAlertDialog( - title: Text(translate("Edit Tag")), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: - const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), - child: Wrap( - children: tags - .map((e) => buildTag(e, selectedTag, onTap: () { - if (selectedTag.contains(e)) { - selectedTag.remove(e); - } else { - selectedTag.add(e); - } - })) - .toList(growable: false), - ), - ), - Offstage( - offstage: !isInProgress, child: const LinearProgressIndicator()) - ], + @override + Widget build(BuildContext context) { + final body = GestureDetector( + onTap: onTap, + child: Obx( + () => Container( + decoration: BoxDecoration( + color: tags.contains(name) ? Colors.blue : null, + border: Border.all(color: MyTheme.darkGray), + borderRadius: BorderRadius.circular(6)), + margin: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 8.0), + padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 8.0), + child: Text(name, + style: + TextStyle(color: tags.contains(name) ? Colors.white : null)), ), - actions: [ - TextButton(onPressed: close, child: Text(translate("Cancel"))), - TextButton(onPressed: submit, child: Text(translate("OK"))), - ], - onSubmit: submit, - onCancel: close, - ); - }); + ), + ); + return useContextMenuArea + ? ContextMenuArea( + width: 100, + builder: (context) => [ + ListTile( + title: Text(translate("Delete")), + onTap: () { + gFFI.abModel.deleteTag(name); + gFFI.abModel.pushAb(); + Future.delayed(Duration.zero, () => Get.back()); + }, + ) + ], + child: body, + ) + : body; } } diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index d4a418262..ce925984e 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -1,6 +1,6 @@ -import 'package:contextmenu/contextmenu.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_hbb/common/widgets/address_book.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:get/get.dart'; @@ -774,7 +774,9 @@ class AddressBookPeerCard extends BasePeerCard { if (await bind.mainPeerHasPassword(id: peer.id)) { menuItems.add(_unrememberPasswordAction(peer.id)); } - menuItems.add(_editTagAction(peer.id)); + if (gFFI.abModel.tags.isNotEmpty) { + menuItems.add(_editTagAction(peer.id)); + } return menuItems; } @@ -836,17 +838,20 @@ class AddressBookPeerCard extends BasePeerCard { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: - const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + padding: const EdgeInsets.symmetric(vertical: 8.0), child: Wrap( children: tags - .map((e) => _buildTag(e, selectedTag, onTap: () { + .map((e) => AddressBookTag( + name: e, + tags: selectedTag, + onTap: () { if (selectedTag.contains(e)) { selectedTag.remove(e); } else { selectedTag.add(e); } - })) + }, + useContextMenuArea: false)) .toList(growable: false), ), ), @@ -863,41 +868,6 @@ class AddressBookPeerCard extends BasePeerCard { ); }); } - - Widget _buildTag(String tagName, RxList rxTags, - {Function()? onTap}) { - return ContextMenuArea( - width: 100, - builder: (context) => [ - ListTile( - title: Text(translate("Delete")), - onTap: () { - gFFI.abModel.deleteTag(tagName); - gFFI.abModel.pushAb(); - Future.delayed(Duration.zero, () => Get.back()); - }, - ) - ], - child: GestureDetector( - onTap: onTap, - child: Obx( - () => Container( - decoration: BoxDecoration( - color: rxTags.contains(tagName) ? Colors.blue : null, - border: Border.all(color: MyTheme.darkGray), - borderRadius: BorderRadius.circular(10)), - margin: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 8.0), - padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 8.0), - child: Text( - tagName, - style: TextStyle( - color: rxTags.contains(tagName) ? Colors.white : null), - ), - ), - ), - ), - ); - } } void _rdpDialog(String id) async { From 14d390e23f498d28be8daf5ecddf1675dae2ace2 Mon Sep 17 00:00:00 2001 From: csf Date: Sat, 8 Oct 2022 19:28:20 +0900 Subject: [PATCH 6/8] opt: address book action more desktop style --- flutter/lib/common/widgets/address_book.dart | 73 +++++++++++------- flutter/lib/common/widgets/peer_card.dart | 76 ++++++++++--------- flutter/lib/consts.dart | 1 + .../lib/desktop/pages/connection_page.dart | 8 +- 4 files changed, 90 insertions(+), 68 deletions(-) diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index 6bf6a76c4..8ced1c0e5 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -1,6 +1,10 @@ import 'package:contextmenu/contextmenu.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hbb/common/widgets/peer_card.dart'; import 'package:flutter_hbb/common/widgets/peers_view.dart'; +import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; +import '../../consts.dart'; +import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; import 'package:get/get.dart'; import '../../common.dart'; @@ -102,6 +106,7 @@ class _AddressBookState extends State { } Widget _buildAddressBook(BuildContext context) { + var pos = RelativeRect.fill; return Row( children: [ Card( @@ -120,27 +125,15 @@ class _AddressBookState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - // TODO same style as peer Text(translate('Tags')), - InkWell( - child: PopupMenuButton( - itemBuilder: (context) => [ - PopupMenuItem( - value: 'add-id', - child: Text(translate("Add ID")), - ), - PopupMenuItem( - value: 'add-tag', - child: Text(translate("Add Tag")), - ), - PopupMenuItem( - value: 'unset-all-tag', - child: Text(translate("Unselect all tags")), - ), - ], - onSelected: handleAbOp, - child: const Icon(Icons.more_vert_outlined)), - ) + GestureDetector( + onTapDown: (e) { + final x = e.globalPosition.dx; + final y = e.globalPosition.dy; + pos = RelativeRect.fromLTRB(x, y, x, y); + }, + onTap: () => _showMenu(pos), + child: ActionMore()), ], ), Expanded( @@ -184,15 +177,39 @@ class _AddressBookState extends State { ); } - /// tag operation - void handleAbOp(String value) { - if (value == 'add-id') { - abAddId(); - } else if (value == 'add-tag') { - abAddTag(); - } else if (value == 'unset-all-tag') { - gFFI.abModel.unsetSelectedTags(); + void _showMenu(RelativeRect pos) { + MenuEntryButton getEntry(String title, VoidCallback proc) { + return MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + title, + style: style, + ), + proc: proc, + padding: kDesktopMenuPadding, + dismissOnClicked: true, + ); } + + final items = [ + getEntry(translate("Add ID"), abAddId), + getEntry(translate("Add Tag"), abAddTag), + getEntry(translate("Unselect all tags"), gFFI.abModel.unsetSelectedTags), + ]; + + 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 abAddId() async { diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index ce925984e..e4838f40f 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -12,7 +12,7 @@ import '../../models/platform_model.dart'; import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; import '../../desktop/widgets/popup_menu.dart'; -class _PopupMenuTheme { +class CustomPopupMenuTheme { static const Color commonColor = MyTheme.accent; // kMinInteractiveDimension static const double height = 20.0; @@ -46,9 +46,8 @@ class _PeerCard extends StatefulWidget { class _PeerCardState extends State<_PeerCard> with AutomaticKeepAliveClientMixin { var _menuPos = RelativeRect.fill; - final double _cardRadis = 16; + final double _cardRadius = 16; final double _borderWidth = 2; - final RxBool _iconMoreHover = false.obs; @override Widget build(BuildContext context) { @@ -122,23 +121,23 @@ class _PeerCardState extends State<_PeerCard> var deco = Rx(BoxDecoration( border: Border.all(color: Colors.transparent, width: _borderWidth), borderRadius: peerCardUiType.value == PeerUiType.grid - ? BorderRadius.circular(_cardRadis) + ? BorderRadius.circular(_cardRadius) : null)); return MouseRegion( onEnter: (evt) { deco.value = BoxDecoration( border: Border.all( - color: Theme.of(context).colorScheme.secondary, + color: Theme.of(context).colorScheme.primary, width: _borderWidth), borderRadius: peerCardUiType.value == PeerUiType.grid - ? BorderRadius.circular(_cardRadis) + ? BorderRadius.circular(_cardRadius) : null); }, onExit: (evt) { deco.value = BoxDecoration( border: Border.all(color: Colors.transparent, width: _borderWidth), borderRadius: peerCardUiType.value == PeerUiType.grid - ? BorderRadius.circular(_cardRadis) + ? BorderRadius.circular(_cardRadius) : null); }, child: GestureDetector( @@ -221,7 +220,7 @@ class _PeerCardState extends State<_PeerCard> () => Container( foregroundDecoration: deco.value, child: ClipRRect( - borderRadius: BorderRadius.circular(_cardRadis - _borderWidth), + borderRadius: BorderRadius.circular(_cardRadius - _borderWidth), child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, @@ -299,27 +298,7 @@ class _PeerCardState extends State<_PeerCard> _menuPos = RelativeRect.fromLTRB(x, y, x, y); }, onPointerUp: (_) => _showPeerMenu(peer.id), - child: MouseRegion( - onEnter: (_) => _iconMoreHover.value = true, - onExit: (_) => _iconMoreHover.value = false, - child: CircleAvatar( - radius: 14, - backgroundColor: _iconMoreHover.value - ? Theme.of(context).scaffoldBackgroundColor - : Theme.of(context).backgroundColor, - // ? Theme.of(context).scaffoldBackgroundColor! - // : Theme.of(context).backgroundColor!, - child: Icon(Icons.more_vert, - size: 18, - color: _iconMoreHover.value - ? Theme.of(context).textTheme.titleLarge?.color - : Theme.of(context) - .textTheme - .titleLarge - ?.color - ?.withOpacity(0.5))))); - // ? MyTheme.color(context).text - // : MyTheme.color(context).lightText)))); + child: ActionMore()); /// Show the peer menu and handle user's choice. /// User might remove the peer or send a file to the peer. @@ -358,9 +337,9 @@ abstract class BasePeerCard extends StatelessWidget { .map((e) => e.build( context, const MenuConfig( - commonColor: _PopupMenuTheme.commonColor, - height: _PopupMenuTheme.height, - dividerHeight: _PopupMenuTheme.dividerHeight))) + commonColor: CustomPopupMenuTheme.commonColor, + height: CustomPopupMenuTheme.height, + dividerHeight: CustomPopupMenuTheme.dividerHeight))) .expand((i) => i) .toList(); @@ -426,7 +405,7 @@ abstract class BasePeerCard extends StatelessWidget { return MenuEntryButton( childBuilder: (TextStyle? style) => Container( alignment: AlignmentDirectional.center, - height: _PopupMenuTheme.height, + height: CustomPopupMenuTheme.height, child: Row( children: [ Text( @@ -875,7 +854,7 @@ void _rdpDialog(String id) async { text: await bind.mainGetPeerOption(id: id, key: 'rdp_port')); final userController = TextEditingController( text: await bind.mainGetPeerOption(id: id, key: 'rdp_username')); - final passwordContorller = TextEditingController( + final passwordController = TextEditingController( text: await bind.mainGetPeerOption(id: id, key: 'rdp_password')); RxBool secure = true.obs; @@ -886,7 +865,7 @@ void _rdpDialog(String id) async { await bind.mainSetPeerOption( id: id, key: 'rdp_username', value: userController.text); await bind.mainSetPeerOption( - id: id, key: 'rdp_password', value: passwordContorller.text); + id: id, key: 'rdp_password', value: passwordController.text); close(); } @@ -970,7 +949,7 @@ void _rdpDialog(String id) async { icon: Icon(secure.value ? Icons.visibility_off : Icons.visibility))), - controller: passwordContorller, + controller: passwordController, )), ), ], @@ -997,3 +976,28 @@ Widget getOnline(double rightPadding, bool online) { child: CircleAvatar( radius: 3, backgroundColor: online ? Colors.green : kColorWarn))); } + +class ActionMore extends StatelessWidget { + final RxBool _iconMoreHover = false.obs; + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) => _iconMoreHover.value = true, + onExit: (_) => _iconMoreHover.value = false, + child: Obx(() => CircleAvatar( + radius: 14, + backgroundColor: _iconMoreHover.value + ? Theme.of(context).scaffoldBackgroundColor + : Theme.of(context).backgroundColor, + child: Icon(Icons.more_vert, + size: 18, + color: _iconMoreHover.value + ? Theme.of(context).textTheme.titleLarge?.color + : Theme.of(context) + .textTheme + .titleLarge + ?.color + ?.withOpacity(0.5))))); + } +} diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index c8dcbbfba..9e46db0d2 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -32,6 +32,7 @@ const kDefaultMouseWheelThrottleDuration = Duration(milliseconds: 50); const kFullScreenEdgeSize = 0.0; var kWindowEdgeSize = Platform.isWindows ? 1.0 : 5.0; const kWindowBorderWidth = 1.0; +const kDesktopMenuPadding = EdgeInsets.only(left: 12.0, right: 3.0); const kInvalidValueStr = "InvalidValueStr"; diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index 480fd1518..9a606d122 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -86,16 +86,16 @@ class _ConnectionPageState extends State ], children: [ RecentPeersView( - menuPadding: EdgeInsets.only(left: 12.0, right: 3.0), + menuPadding: kDesktopMenuPadding, ), FavoritePeersView( - menuPadding: EdgeInsets.only(left: 12.0, right: 3.0), + menuPadding: kDesktopMenuPadding, ), DiscoveredPeersView( - menuPadding: EdgeInsets.only(left: 12.0, right: 3.0), + menuPadding: kDesktopMenuPadding, ), const AddressBook( - menuPadding: EdgeInsets.only(left: 12.0, right: 3.0), + menuPadding: kDesktopMenuPadding, ), ], ).paddingOnly(right: 12.0), From dc8ddc4364cd95c49dbd5942d06700d669904ea4 Mon Sep 17 00:00:00 2001 From: csf Date: Sat, 8 Oct 2022 19:52:02 +0900 Subject: [PATCH 7/8] opt: address book tag action menu desktop style --- flutter/lib/common/widgets/address_book.dart | 84 ++++++++++++-------- flutter/lib/common/widgets/peer_card.dart | 2 +- 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index 8ced1c0e5..49d2eaf04 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -1,4 +1,3 @@ -import 'package:contextmenu/contextmenu.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/widgets/peer_card.dart'; import 'package:flutter_hbb/common/widgets/peers_view.dart'; @@ -178,18 +177,6 @@ class _AddressBookState extends State { } void _showMenu(RelativeRect pos) { - MenuEntryButton getEntry(String title, VoidCallback proc) { - return MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - title, - style: style, - ), - proc: proc, - padding: kDesktopMenuPadding, - dismissOnClicked: true, - ); - } - final items = [ getEntry(translate("Add ID"), abAddId), getEntry(translate("Add Tag"), abAddTag), @@ -355,20 +342,32 @@ class AddressBookTag extends StatelessWidget { final String name; final RxList tags; final Function()? onTap; - final bool useContextMenuArea; + final bool showActionMenu; const AddressBookTag( {Key? key, required this.name, required this.tags, this.onTap, - this.useContextMenuArea = true}) + this.showActionMenu = true}) : super(key: key); @override Widget build(BuildContext context) { - final body = GestureDetector( + 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); + } + + 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( @@ -383,21 +382,42 @@ class AddressBookTag extends StatelessWidget { ), ), ); - return useContextMenuArea - ? ContextMenuArea( - width: 100, - builder: (context) => [ - ListTile( - title: Text(translate("Delete")), - onTap: () { - gFFI.abModel.deleteTag(name); - gFFI.abModel.pushAb(); - Future.delayed(Duration.zero, () => Get.back()); - }, - ) - ], - child: body, - ) - : body; + } + + void _showMenu(BuildContext context, RelativeRect pos) { + final items = [ + getEntry(translate("Delete"), () { + gFFI.abModel.deleteTag(name); + gFFI.abModel.pushAb(); + Future.delayed(Duration.zero, () => Get.back()); + }), + ]; + + mod_menu.showMenu( + context: context, + position: pos, + items: items + .map((e) => e.build( + context, + MenuConfig( + commonColor: CustomPopupMenuTheme.commonColor, + height: CustomPopupMenuTheme.height, + dividerHeight: CustomPopupMenuTheme.dividerHeight))) + .expand((i) => i) + .toList(), + elevation: 8, + ); } } + +MenuEntryButton getEntry(String title, VoidCallback proc) { + return MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + title, + style: style, + ), + proc: proc, + padding: kDesktopMenuPadding, + dismissOnClicked: true, + ); +} diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index e4838f40f..19d28513c 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -830,7 +830,7 @@ class AddressBookPeerCard extends BasePeerCard { selectedTag.add(e); } }, - useContextMenuArea: false)) + showActionMenu: false)) .toList(growable: false), ), ), From c418a333841af89cd35d2af909343d7f96d235b1 Mon Sep 17 00:00:00 2001 From: csf Date: Sat, 8 Oct 2022 19:56:04 +0900 Subject: [PATCH 8/8] fix: del selectedTags before tags --- flutter/lib/models/ab_model.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index 30da5e52d..ae41e07e6 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -117,6 +117,7 @@ class AbModel { } void deleteTag(String tag) { + gFFI.abModel.selectedTags.remove(tag); tags.removeWhere((element) => element == tag); for (var peer in peers) { if (peer.tags.isEmpty) {