diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index a0a456807..78bd20ef0 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -241,14 +241,15 @@ class _AddressBookState extends State { 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), - ]), - )), + 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), @@ -358,7 +359,6 @@ class _AddressBookState extends State { alignment: Alignment.topLeft, child: AddressBookPeersView( menuPadding: widget.menuPadding, - getInitPeers: () => gFFI.abModel.currentAbPeers, )), ); } @@ -509,19 +509,19 @@ class _AddressBookState extends State { 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); + 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)); } @@ -546,23 +546,28 @@ class _AddressBookState extends State { ], ), input: Obx(() => TextField( - controller: idController, - inputFormatters: [IDTextInputFormatter()], - decoration: InputDecoration( - labelText: stateGlobal.isPortrait.isFalse ? null : translate('ID'), - errorText: errorMsg, - errorMaxLines: 5), - ))), + 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'), - ),)), + controller: aliasController, + decoration: InputDecoration( + labelText: stateGlobal.isPortrait.isFalse + ? null + : translate('Alias'), + ), + )), ), if (isCurrentAbShared) row( @@ -570,25 +575,29 @@ class _AddressBookState extends State { 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; - }); - }, + 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, diff --git a/flutter/lib/common/widgets/my_group.dart b/flutter/lib/common/widgets/my_group.dart index 2d26536eb..867d71dff 100644 --- a/flutter/lib/common/widgets/my_group.dart +++ b/flutter/lib/common/widgets/my_group.dart @@ -83,8 +83,8 @@ class _MyGroupState extends State { child: Align( alignment: Alignment.topLeft, child: MyGroupPeerView( - menuPadding: widget.menuPadding, - getInitPeers: () => gFFI.groupModel.peers)), + menuPadding: widget.menuPadding, + )), ) ], ); @@ -115,8 +115,8 @@ class _MyGroupState extends State { child: Align( alignment: Alignment.topLeft, child: MyGroupPeerView( - menuPadding: widget.menuPadding, - getInitPeers: () => gFFI.groupModel.peers)), + menuPadding: widget.menuPadding, + )), ) ], ); diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index b18de82d9..32db418f5 100644 --- a/flutter/lib/common/widgets/peers_view.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -6,6 +6,7 @@ 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:flutter_hbb/models/peer_tab_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; @@ -42,6 +43,14 @@ class LoadEvent { static const String group = 'load_group_peers'; } +class PeersModelName { + static const String recent = 'recent peer'; + static const String favorite = 'fav peer'; + static const String lan = 'discovered peer'; + static const String addressBook = 'address book peer'; + static const String group = 'group peer'; +} + /// for peer search text, global obs value final peerSearchText = "".obs; @@ -128,8 +137,9 @@ class _PeersViewState extends State<_PeersView> // // Although `onWindowRestore()` is called after `onWindowBlur()` in my test, // we need the following comparison to ensure that `_isActive` is true in the end. - if (isWindows && DateTime.now().difference(_lastWindowRestoreTime) < - const Duration(milliseconds: 300)) { + if (isWindows && + DateTime.now().difference(_lastWindowRestoreTime) < + const Duration(milliseconds: 300)) { return; } _queryCount = _maxQueryCount; @@ -170,8 +180,8 @@ class _PeersViewState extends State<_PeersView> // We should avoid too many rebuilds. MacOS(m1, 14.6.1) on Flutter 3.19.6. // Continious rebuilds of `ChangeNotifierProvider` will cause memory leak. // Simple demo can reproduce this issue. - return ChangeNotifierProvider( - create: (context) => widget.peers, + return ChangeNotifierProvider.value( + value: widget.peers, child: Consumer(builder: (context, peers, child) { if (peers.peers.isEmpty) { gFFI.peerTabModel.setCurrentTabCachedPeers([]); @@ -403,28 +413,39 @@ class _PeersViewState extends State<_PeersView> } abstract class BasePeersView extends StatelessWidget { - final String name; - final String loadEvent; + final PeerTabIndex peerTabIndex; final PeerFilter? peerFilter; final PeerCardBuilder peerCardBuilder; - final GetInitPeers? getInitPeers; const BasePeersView({ Key? key, - required this.name, - required this.loadEvent, + required this.peerTabIndex, this.peerFilter, required this.peerCardBuilder, - required this.getInitPeers, }) : super(key: key); @override Widget build(BuildContext context) { + Peers peers; + switch (peerTabIndex) { + case PeerTabIndex.recent: + peers = gFFI.recentPeersModel; + break; + case PeerTabIndex.fav: + peers = gFFI.favoritePeersModel; + break; + case PeerTabIndex.lan: + peers = gFFI.lanPeersModel; + break; + case PeerTabIndex.ab: + peers = gFFI.abModel.peersModel; + break; + case PeerTabIndex.group: + peers = gFFI.groupModel.peersModel; + break; + } return _PeersView( - peers: - Peers(name: name, loadEvent: loadEvent, getInitPeers: getInitPeers), - peerFilter: peerFilter, - peerCardBuilder: peerCardBuilder); + peers: peers, peerFilter: peerFilter, peerCardBuilder: peerCardBuilder); } } @@ -433,13 +454,11 @@ class RecentPeersView extends BasePeersView { {Key? key, EdgeInsets? menuPadding, ScrollController? scrollController}) : super( key: key, - name: 'recent peer', - loadEvent: LoadEvent.recent, + peerTabIndex: PeerTabIndex.recent, peerCardBuilder: (Peer peer) => RecentPeerCard( peer: peer, menuPadding: menuPadding, ), - getInitPeers: null, ); @override @@ -455,13 +474,11 @@ class FavoritePeersView extends BasePeersView { {Key? key, EdgeInsets? menuPadding, ScrollController? scrollController}) : super( key: key, - name: 'favorite peer', - loadEvent: LoadEvent.favorite, + peerTabIndex: PeerTabIndex.fav, peerCardBuilder: (Peer peer) => FavoritePeerCard( peer: peer, menuPadding: menuPadding, ), - getInitPeers: null, ); @override @@ -477,13 +494,11 @@ class DiscoveredPeersView extends BasePeersView { {Key? key, EdgeInsets? menuPadding, ScrollController? scrollController}) : super( key: key, - name: 'discovered peer', - loadEvent: LoadEvent.lan, + peerTabIndex: PeerTabIndex.lan, peerCardBuilder: (Peer peer) => DiscoveredPeerCard( peer: peer, menuPadding: menuPadding, ), - getInitPeers: null, ); @override @@ -496,21 +511,16 @@ class DiscoveredPeersView extends BasePeersView { class AddressBookPeersView extends BasePeersView { AddressBookPeersView( - {Key? key, - EdgeInsets? menuPadding, - ScrollController? scrollController, - required GetInitPeers getInitPeers}) + {Key? key, EdgeInsets? menuPadding, ScrollController? scrollController}) : super( key: key, - name: 'address book peer', - loadEvent: LoadEvent.addressBook, + peerTabIndex: PeerTabIndex.ab, peerFilter: (Peer peer) => _hitTag(gFFI.abModel.selectedTags, peer.tags), peerCardBuilder: (Peer peer) => AddressBookPeerCard( peer: peer, menuPadding: menuPadding, ), - getInitPeers: getInitPeers, ); static bool _hitTag(List selectedTags, List idents) { @@ -537,20 +547,15 @@ class AddressBookPeersView extends BasePeersView { class MyGroupPeerView extends BasePeersView { MyGroupPeerView( - {Key? key, - EdgeInsets? menuPadding, - ScrollController? scrollController, - required GetInitPeers getInitPeers}) + {Key? key, EdgeInsets? menuPadding, ScrollController? scrollController}) : super( key: key, - name: 'group peer', - loadEvent: LoadEvent.group, + peerTabIndex: PeerTabIndex.group, peerFilter: filter, peerCardBuilder: (Peer peer) => MyGroupPeerCard( peer: peer, menuPadding: menuPadding, ), - getInitPeers: getInitPeers, ); static bool filter(Peer peer) { diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index 6f3820e86..0da84e0f2 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -66,10 +66,16 @@ class AbModel { var listInitialized = false; var _maxPeerOneAb = 0; + late final Peers peersModel; + WeakReference parent; AbModel(this.parent) { addressbooks.clear(); + peersModel = Peers( + name: PeersModelName.addressBook, + getInitPeers: () => currentAbPeers, + loadEvent: LoadEvent.addressBook); if (desktopType == DesktopType.main) { Timer.periodic(Duration(milliseconds: 500), (timer) async { if (_timerCounter++ % 6 == 0) { diff --git a/flutter/lib/models/group_model.dart b/flutter/lib/models/group_model.dart index 184c94bff..b14ccd46b 100644 --- a/flutter/lib/models/group_model.dart +++ b/flutter/lib/models/group_model.dart @@ -23,7 +23,14 @@ class GroupModel { bool get emtpy => users.isEmpty && peers.isEmpty; - GroupModel(this.parent); + late final Peers peersModel; + + GroupModel(this.parent) { + peersModel = Peers( + name: PeersModelName.group, + getInitPeers: () => peers, + loadEvent: LoadEvent.group); + } Future pull({force = true, quiet = false}) async { if (bind.isDisableGroupPanel()) return; diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 899affa62..3f2dcade9 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -7,12 +7,14 @@ import 'dart:ui' as ui; import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_hbb/common/widgets/peers_view.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/models/ab_model.dart'; import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/cm_file_model.dart'; import 'package:flutter_hbb/models/file_model.dart'; import 'package:flutter_hbb/models/group_model.dart'; +import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/models/peer_tab_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/models/user_model.dart'; @@ -2397,6 +2399,9 @@ class FFI { late final ElevationModel elevationModel; // session late final CmFileModel cmFileModel; // cm late final TextureModel textureModel; //session + late final Peers recentPeersModel; // global + late final Peers favoritePeersModel; // global + late final Peers lanPeersModel; // global FFI(SessionID? sId) { sessionId = sId ?? (isDesktop ? Uuid().v4obj() : _constSessionId); @@ -2417,6 +2422,16 @@ class FFI { elevationModel = ElevationModel(WeakReference(this)); cmFileModel = CmFileModel(WeakReference(this)); textureModel = TextureModel(WeakReference(this)); + recentPeersModel = Peers( + name: PeersModelName.recent, + loadEvent: LoadEvent.recent, + getInitPeers: null); + favoritePeersModel = Peers( + name: PeersModelName.favorite, + loadEvent: LoadEvent.favorite, + getInitPeers: null); + lanPeersModel = Peers( + name: PeersModelName.lan, loadEvent: LoadEvent.lan, getInitPeers: null); } /// Mobile reuse FFI