From 8d23c11312cd001cb905ef631cc971595a72b27c Mon Sep 17 00:00:00 2001 From: csf Date: Sun, 9 Oct 2022 19:41:50 +0900 Subject: [PATCH 1/4] fix abModel multi request and state didn't refresh bug --- flutter/lib/common/widgets/address_book.dart | 62 +++---- flutter/lib/common/widgets/peer_tab_page.dart | 3 +- .../lib/desktop/pages/desktop_home_page.dart | 1 - .../desktop/pages/desktop_setting_page.dart | 14 +- flutter/lib/main.dart | 3 - flutter/lib/mobile/pages/connection_page.dart | 16 +- flutter/lib/mobile/pages/scan_page.dart | 7 +- flutter/lib/mobile/pages/settings_page.dart | 168 ++---------------- flutter/lib/models/ab_model.dart | 3 +- flutter/lib/models/model.dart | 4 - flutter/lib/models/user_model.dart | 60 ++++++- 11 files changed, 111 insertions(+), 230 deletions(-) diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index 49d2eaf04..52189c8b1 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -9,7 +9,6 @@ import 'package:get/get.dart'; import '../../common.dart'; import '../../desktop/pages/desktop_home_page.dart'; import '../../mobile/pages/settings_page.dart'; -import '../../models/platform_model.dart'; class AddressBook extends StatefulWidget { final EdgeInsets? menuPadding; @@ -30,7 +29,7 @@ class _AddressBookState extends State { @override Widget build(BuildContext context) => FutureBuilder( - future: buildAddressBook(context), + future: buildBody(context), builder: (context, snapshot) { if (snapshot.hasData) { return snapshot.data!; @@ -44,7 +43,7 @@ class _AddressBookState extends State { if (isDesktop) { loginDialog().then((success) { if (success) { - setState(() {}); + gFFI.abModel.pullAb(); } }); } else { @@ -52,41 +51,30 @@ class _AddressBookState extends State { } } - Future buildAddressBook(BuildContext context) async { - final token = await bind.mainGetLocalOption(key: 'access_token'); - if (token.trim().isEmpty) { - return Center( - child: InkWell( - onTap: handleLogin, - child: Text( - translate("Login"), - style: const TextStyle(decoration: TextDecoration.underline), + Future buildBody(BuildContext context) async { + return Obx(() { + if (gFFI.userModel.userName.value.isEmpty) { + return Center( + child: InkWell( + onTap: handleLogin, + child: Text( + translate("Login"), + style: const TextStyle(decoration: TextDecoration.underline), + ), ), - ), - ); - } - final model = gFFI.abModel; - return FutureBuilder( - future: model.pullAb(), - builder: (context, snapshot) { - if (snapshot.hasData) { - return _buildAddressBook(context); - } else if (snapshot.hasError) { - return _buildShowError(snapshot.error.toString()); - } else { - 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(); - } - }); - } - }); + ); + } else { + if (gFFI.abModel.abLoading.value) { + return const Center( + child: CircularProgressIndicator(), + ); + } + if (gFFI.abModel.abError.isNotEmpty) { + return _buildShowError(gFFI.abModel.abError.value); + } + return _buildAddressBook(context); + } + }); } Widget _buildShowError(String error) { diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index 9a5503e26..81559a3d3 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -54,7 +54,8 @@ class _PeerTabPageState extends State bind.mainDiscover(); break; case 3: - gFFI.abModel.pullAb(); + + /// AddressBook initState will refresh ab state break; } } diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index fcc8c4991..6cb78b6a7 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -500,7 +500,6 @@ Future loginDialog() async { close(); } - // 登录dialog return CustomAlertDialog( title: Text(translate("Login")), content: ConstrainedBox( diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 1c28fdd98..1534f9394 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -761,20 +761,18 @@ class _AccountState extends State<_Account> { Widget accountAction() { return _futureBuilder(future: () async { return await gFFI.userModel.getUserName(); - }(), hasData: (data) { - String username = data as String; - return _Button( - username.isEmpty ? 'Login' : 'Logout', + }(), hasData: (_) { + return Obx(() => _Button( + gFFI.userModel.userName.value.isEmpty ? 'Login' : 'Logout', () => { - username.isEmpty + gFFI.userModel.userName.value.isEmpty ? loginDialog().then((success) { if (success) { - // refresh frame - setState(() {}); + gFFI.abModel.pullAb(); } }) : gFFI.userModel.logOut() - }); + })); }); } } diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 230199431..0d1123e05 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -20,7 +20,6 @@ import 'common.dart'; import 'consts.dart'; import 'mobile/pages/home_page.dart'; import 'mobile/pages/server_page.dart'; -import 'mobile/pages/settings_page.dart'; import 'models/platform_model.dart'; int? windowId; @@ -82,7 +81,6 @@ Future initEnv(String appType) async { // focus on multi-ffi on desktop first await initGlobalFFI(); // await Firebase.initializeApp(); - refreshCurrentUser(); _registerEventHandler(); } @@ -267,7 +265,6 @@ class _AppState extends State { ChangeNotifierProvider.value(value: gFFI.imageModel), ChangeNotifierProvider.value(value: gFFI.cursorModel), ChangeNotifierProvider.value(value: gFFI.canvasModel), - ChangeNotifierProvider.value(value: gFFI.userModel), ], child: GetMaterialApp( navigatorKey: globalKey, diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index 9d105ade6..e99226c4d 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -206,20 +206,14 @@ class WebMenu extends StatefulWidget { } class _WebMenuState extends State { - String? username; String url = ""; @override void initState() { super.initState(); () async { - final usernameRes = await getUsername(); - final urlRes = await getUrl(); + final urlRes = await bind.mainGetApiServer(); var update = false; - if (usernameRes != username) { - username = usernameRes; - update = true; - } if (urlRes != url) { url = urlRes; update = true; @@ -256,9 +250,9 @@ class _WebMenuState extends State { : [ PopupMenuItem( value: "login", - child: Text(username == null + child: Text(gFFI.userModel.userName.value.isEmpty ? translate("Login") - : '${translate("Logout")} ($username)'), + : '${translate("Logout")} (${gFFI.userModel.userName.value})'), ) ]) + [ @@ -276,10 +270,10 @@ class _WebMenuState extends State { showAbout(gFFI.dialogManager); } if (value == 'login') { - if (username == null) { + if (gFFI.userModel.userName.value.isEmpty) { showLogin(gFFI.dialogManager); } else { - logout(gFFI.dialogManager); + gFFI.userModel.logOut(); } } if (value == 'scan') { diff --git a/flutter/lib/mobile/pages/scan_page.dart b/flutter/lib/mobile/pages/scan_page.dart index 2487c0f58..3bd381d92 100644 --- a/flutter/lib/mobile/pages/scan_page.dart +++ b/flutter/lib/mobile/pages/scan_page.dart @@ -263,12 +263,13 @@ void showServerSettingsWithValue(String id, String relay, String key, if (id != id0) { bind.mainSetOption(key: "custom-rendezvous-server", value: id); } - if (relay != relay0) + if (relay != relay0) { bind.mainSetOption(key: "relay-server", value: relay); + } if (key != key0) bind.mainSetOption(key: "key", value: key); - if (api != api0) + if (api != api0) { bind.mainSetOption(key: "api-server", value: api); - gFFI.ffiModel.updateUser(); + } close(); } setState(() { diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index 4f1640a03..c555314d0 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:http/http.dart' as http; import 'package:provider/provider.dart'; import 'package:settings_ui/settings_ui.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -41,8 +40,6 @@ var _localIP = ""; var _directAccessPort = ""; class _SettingsState extends State with WidgetsBindingObserver { - String? username; - @override void initState() { super.initState(); @@ -54,12 +51,6 @@ class _SettingsState extends State with WidgetsBindingObserver { update = await updateIgnoreBatteryStatus(); } - final usernameRes = await getUsername(); - if (usernameRes != username) { - update = true; - username = usernameRes; - } - final enableAbrRes = await bind.mainGetOption(key: "enable-abr") != "N"; if (enableAbrRes != _enableAbr) { update = true; @@ -273,15 +264,15 @@ class _SettingsState extends State with WidgetsBindingObserver { title: Text(translate("Account")), tiles: [ SettingsTile.navigation( - title: Text(username == null + title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty ? translate("Login") - : '${translate("Logout")} ($username)'), + : '${translate("Logout")} (${gFFI.userModel.userName.value})')), leading: Icon(Icons.person), onPressed: (context) { - if (username == null) { + if (gFFI.userModel.userName.value.isEmpty) { showLogin(gFFI.dialogManager); } else { - logout(gFFI.dialogManager); + gFFI.userModel.logOut(); } }, ), @@ -438,130 +429,6 @@ void showAbout(OverlayDialogManager dialogManager) { }, clickMaskDismiss: true, backDismiss: true); } -Future login(String name, String pass) async { -/* js test CORS -const data = { username: 'example', password: 'xx' }; - -fetch('http://localhost:21114/api/login', { - method: 'POST', // or 'PUT' - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), -}) -.then(response => response.json()) -.then(data => { - console.log('Success:', data); -}) -.catch((error) => { - console.error('Error:', error); -}); -*/ - final url = getUrl(); - final body = { - 'username': name, - 'password': pass, - 'id': bind.mainGetMyId(), - 'uuid': bind.mainGetUuid() - }; - try { - final response = await http.post(Uri.parse('$url/api/login'), - headers: {"Content-Type": "application/json"}, body: json.encode(body)); - return parseResp(response.body); - } catch (e) { - print(e); - return 'Failed to access $url'; - } -} - -String parseResp(String body) { - final data = json.decode(body); - final error = data['error']; - if (error != null) { - return error!; - } - final token = data['access_token']; - if (token != null) { - bind.mainSetOption(key: "access_token", value: token); - } - final info = data['user']; - if (info != null) { - final value = json.encode(info); - bind.mainSetOption(key: "user_info", value: value); - gFFI.ffiModel.updateUser(); - } - return ''; -} - -void refreshCurrentUser() async { - final token = await bind.mainGetOption(key: "access_token"); - if (token == '') return; - final url = getUrl(); - final body = {'id': bind.mainGetMyId(), 'uuid': bind.mainGetUuid()}; - try { - final response = await http.post(Uri.parse('$url/api/currentUser'), - headers: { - "Content-Type": "application/json", - "Authorization": "Bearer $token" - }, - body: json.encode(body)); - final status = response.statusCode; - if (status == 401 || status == 400) { - resetToken(); - return; - } - parseResp(response.body); - } catch (e) { - print('$e'); - } -} - -void logout(OverlayDialogManager dialogManager) async { - final token = await bind.mainGetOption(key: "access_token"); - if (token == '') return; - final url = getUrl(); - final body = {'id': bind.mainGetMyId(), 'uuid': bind.mainGetUuid()}; - try { - await http.post(Uri.parse('$url/api/logout'), - headers: { - "Content-Type": "application/json", - "Authorization": "Bearer $token" - }, - body: json.encode(body)); - } catch (e) { - showToast('Failed to access $url'); - } - resetToken(); -} - -void resetToken() async { - await bind.mainSetOption(key: "access_token", value: ""); - await bind.mainSetOption(key: "user_info", value: ""); - gFFI.ffiModel.updateUser(); -} - -Future getUrl() async { - var url = await bind.mainGetOption(key: "api-server"); - if (url == '') { - url = await bind.mainGetOption(key: "custom-rendezvous-server"); - if (url != '') { - if (url.contains(':')) { - final tmp = url.split(':'); - if (tmp.length == 2) { - var port = int.parse(tmp[1]) - 2; - url = 'http://${tmp[0]}:$port'; - } - } else { - url = 'http://$url:21114'; - } - } - } - if (url == '') { - url = 'https://admin.rustdesk.com'; - } - return url; -} - void showLogin(OverlayDialogManager dialogManager) { final passwordController = TextEditingController(); final nameController = TextEditingController(); @@ -615,15 +482,17 @@ void showLogin(OverlayDialogManager dialogManager) { setState(() { loading = true; }); - final e = await login(name, pass); + final resp = await gFFI.userModel.login(name, pass); setState(() { loading = false; - error = e; }); - if (e == "") { - close(); + if (resp.containsKey('error')) { + error = resp['error']; + return; } + gFFI.abModel.pullAb(); } + close(); }, child: Text(translate('OK')), ), @@ -632,23 +501,6 @@ void showLogin(OverlayDialogManager dialogManager) { }); } -Future getUsername() async { - final token = await bind.mainGetOption(key: "access_token"); - String? username; - if (token != "") { - final info = await bind.mainGetOption(key: "user_info"); - if (info != "") { - try { - Map tmp = json.decode(info); - username = tmp["name"]; - } catch (e) { - print('$e'); - } - } - } - return username; -} - class ScanButton extends StatelessWidget { @override Widget build(BuildContext context) { diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index ae41e07e6..5a055fd14 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -24,8 +24,9 @@ class AbModel { FFI? get _ffi => parent.target; Future pullAb() async { + if (_ffi!.userModel.userName.isEmpty) return; abLoading.value = true; - // request + abError.value = ""; final api = "${await bind.mainGetApiServer()}/api/ab/get"; try { final resp = diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 833ab32a0..c646f5285 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -82,10 +82,6 @@ class FfiModel with ChangeNotifier { notifyListeners(); } - updateUser() { - notifyListeners(); - } - bool keyboard() => _permissions['keyboard'] != false; clear() { diff --git a/flutter/lib/models/user_model.dart b/flutter/lib/models/user_model.dart index e195c205d..e9990efa9 100644 --- a/flutter/lib/models/user_model.dart +++ b/flutter/lib/models/user_model.dart @@ -9,11 +9,65 @@ import '../common.dart'; import 'model.dart'; import 'platform_model.dart'; -class UserModel extends ChangeNotifier { +class UserModel { var userName = "".obs; WeakReference parent; - UserModel(this.parent); + UserModel(this.parent) { + refreshCurrentUser(); + } + + void refreshCurrentUser() async { + await getUserName(); + final token = await bind.mainGetLocalOption(key: "access_token"); + if (token == '') return; + final url = await bind.mainGetApiServer(); + final body = { + 'id': await bind.mainGetMyId(), + 'uuid': await bind.mainGetUuid() + }; + try { + final response = await http.post(Uri.parse('$url/api/currentUser'), + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer $token" + }, + body: json.encode(body)); + final status = response.statusCode; + if (status == 401 || status == 400) { + resetToken(); + return; + } + await _parseResp(response.body); + } catch (e) { + print('Failed to refreshCurrentUser: $e'); + } + } + + void resetToken() async { + await bind.mainSetLocalOption(key: "access_token", value: ""); + await bind.mainSetLocalOption(key: "user_info", value: ""); + userName.value = ""; + } + + Future _parseResp(String body) async { + final data = json.decode(body); + final error = data['error']; + if (error != null) { + return error!; + } + final token = data['access_token']; + if (token != null) { + await bind.mainSetLocalOption(key: "access_token", value: token); + } + final info = data['user']; + if (info != null) { + final value = json.encode(info); + await bind.mainSetOption(key: "user_info", value: value); + userName.value = info["name"]; + } + return ''; + } Future getUserName() async { if (userName.isNotEmpty) { @@ -29,6 +83,7 @@ class UserModel extends ChangeNotifier { } Future logOut() async { + // TODO show toast debugPrint("start logout"); final url = await bind.mainGetApiServer(); final _ = await http.post(Uri.parse("$url/api/logout"), @@ -44,7 +99,6 @@ class UserModel extends ChangeNotifier { ]); parent.target?.abModel.clear(); userName.value = ""; - notifyListeners(); } Future> login(String userName, String pass) async { From 715d837f5424b99a21ef1866ec3d764f7f4de693 Mon Sep 17 00:00:00 2001 From: csf Date: Sun, 9 Oct 2022 19:57:38 +0900 Subject: [PATCH 2/4] logOut show loading --- flutter/lib/common.dart | 26 +++++++++++++++----------- flutter/lib/models/user_model.dart | 5 ++--- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 2ff3d4477..ce341d160 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -383,22 +383,23 @@ class OverlayDialogManager { "[OverlayDialogManager] Failed to show dialog, _overlayState is null, call [setOverlayState] first"); } - final _tag; + final String dialogTag; if (tag != null) { - _tag = tag; + dialogTag = tag; } else { - _tag = _tagCount.toString(); + dialogTag = _tagCount.toString(); _tagCount++; } final dialog = Dialog(); - _dialogs[_tag] = dialog; + _dialogs[dialogTag] = dialog; - final close = ([res]) { - _dialogs.remove(_tag); + close([res]) { + _dialogs.remove(dialogTag); dialog.complete(res); - BackButtonInterceptor.removeByName(_tag); - }; + BackButtonInterceptor.removeByName(dialogTag); + } + dialog.entry = OverlayEntry(builder: (_) { bool innerClicked = false; return Listener( @@ -423,14 +424,16 @@ class OverlayDialogManager { close(); } return true; - }, name: _tag); + }, name: dialogTag); return dialog.completer.future; } - void showLoading(String text, + String showLoading(String text, {bool clickMaskDismiss = false, bool showCancel = true, VoidCallback? onCancel}) { + final tag = _tagCount.toString(); + _tagCount++; show((setState, close) { cancel() { dismissAll(); @@ -465,7 +468,8 @@ class OverlayDialogManager { ])), onCancel: showCancel ? cancel : null, ); - }); + }, tag: tag); + return tag; } void resetMobileActionsOverlay({FFI? ffi}) { diff --git a/flutter/lib/models/user_model.dart b/flutter/lib/models/user_model.dart index e9990efa9..163efaebc 100644 --- a/flutter/lib/models/user_model.dart +++ b/flutter/lib/models/user_model.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; -import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; @@ -83,8 +82,7 @@ class UserModel { } Future logOut() async { - // TODO show toast - debugPrint("start logout"); + final tag = gFFI.dialogManager.showLoading(translate('Waiting')); final url = await bind.mainGetApiServer(); final _ = await http.post(Uri.parse("$url/api/logout"), body: { @@ -99,6 +97,7 @@ class UserModel { ]); parent.target?.abModel.clear(); userName.value = ""; + gFFI.dialogManager.dismissByTag(tag); } Future> login(String userName, String pass) async { From 1ce8b1fee558822d06d9f6e96af0effd44960057 Mon Sep 17 00:00:00 2001 From: csf Date: Mon, 10 Oct 2022 18:27:26 +0900 Subject: [PATCH 3/4] mobile tag actions --- flutter/lib/common/widgets/address_book.dart | 130 +++++++++++++------ 1 file changed, 89 insertions(+), 41 deletions(-) diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index 52189c8b1..570ff6e95 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -21,6 +21,8 @@ class AddressBook extends StatefulWidget { } class _AddressBookState extends State { + var menuPos = RelativeRect.fill; + @override void initState() { super.initState(); @@ -72,7 +74,9 @@ class _AddressBookState extends State { if (gFFI.abModel.abError.isNotEmpty) { return _buildShowError(gFFI.abModel.abError.value); } - return _buildAddressBook(context); + return isDesktop + ? _buildAddressBookDesktop() + : _buildAddressBookMobile(); } }); } @@ -92,8 +96,7 @@ class _AddressBookState extends State { )); } - Widget _buildAddressBook(BuildContext context) { - var pos = RelativeRect.fill; + Widget _buildAddressBookDesktop() { return Row( children: [ Card( @@ -109,20 +112,7 @@ class _AddressBookState extends State { const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), child: Column( children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(translate('Tags')), - 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()), - ], - ), + _buildTagHeader(), Expanded( child: Container( width: double.infinity, @@ -130,40 +120,98 @@ class _AddressBookState extends State { decoration: BoxDecoration( border: Border.all(color: MyTheme.darkGray), borderRadius: BorderRadius.circular(2)), - child: Obx( - () => Wrap( - children: gFFI.abModel.tags - .map((e) => 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); - } - })) - .toList(), - ), - ), + child: _buildTags(), ).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, - ))), - ) + _buildPeersViews() ], ); } + Widget _buildAddressBookMobile() { + return Column( + children: [ + Card( + margin: EdgeInsets.symmetric(horizontal: 1.0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + side: + BorderSide(color: Theme.of(context).scaffoldBackgroundColor)), + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildTagHeader(), + Container( + width: double.infinity, + decoration: BoxDecoration( + border: Border.all(color: MyTheme.darkGray), + borderRadius: BorderRadius.circular(4)), + child: _buildTags(), + ).marginSymmetric(vertical: 8.0), + ], + ), + ), + ), + Divider(), + _buildPeersViews() + ], + ); + } + + Widget _buildTagHeader() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(translate('Tags')), + GestureDetector( + onTapDown: (e) { + final x = e.globalPosition.dx; + final y = e.globalPosition.dy; + menuPos = RelativeRect.fromLTRB(x, y, x, y); + }, + onTap: () => _showMenu(menuPos), + child: ActionMore()), + ], + ); + } + + Widget _buildTags() { + return Obx( + () => Wrap( + children: gFFI.abModel.tags + .map((e) => 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); + } + })) + .toList(), + ), + ); + } + + Widget _buildPeersViews() { + return Expanded( + child: Align( + alignment: Alignment.topLeft, + child: Obx(() => AddressBookPeersView( + menuPadding: widget.menuPadding, + initPeers: gFFI.abModel.peers.value, + ))), + ); + } + void _showMenu(RelativeRect pos) { final items = [ getEntry(translate("Add ID"), abAddId), From efacc7362adc4dd7a4f6466aa4e7ba1e7b3fcde3 Mon Sep 17 00:00:00 2001 From: csf Date: Mon, 10 Oct 2022 21:10:31 +0900 Subject: [PATCH 4/4] fix hit tag empty space bug --- flutter/lib/common.dart | 17 --------- flutter/lib/common/widgets/peers_view.dart | 44 ++++++++++++++++++---- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index ce341d160..c4615c336 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -936,23 +936,6 @@ Future matchPeer(String searchText, Peer peer) async { return alias.toLowerCase().contains(searchText); } -Future>? matchPeers(String searchText, List peers) async { - searchText = searchText.trim(); - if (searchText.isEmpty) { - return peers; - } - searchText = searchText.toLowerCase(); - final matches = - await Future.wait(peers.map((peer) => matchPeer(searchText, peer))); - final filteredList = List.empty(growable: true); - for (var i = 0; i < peers.length; i++) { - if (matches[i]) { - filteredList.add(peers[i]); - } - } - return filteredList; -} - /// Get the image for the current [platform]. Widget getPlatformImage(String platform, {double size = 50}) { platform = platform.toLowerCase(); diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index 03a2436f2..6e52bfeb8 100644 --- a/flutter/lib/common/widgets/peers_view.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -13,6 +13,7 @@ import '../../models/peer_model.dart'; import '../../models/platform_model.dart'; import 'peer_card.dart'; +typedef PeerFilter = bool Function(Peer peer); typedef PeerCardBuilder = Widget Function(Peer peer); /// for peer search text, global obs value @@ -22,10 +23,14 @@ final peerSearchTextController = class _PeersView extends StatefulWidget { final Peers peers; + final PeerFilter? peerFilter; final PeerCardBuilder peerCardBuilder; const _PeersView( - {required this.peers, required this.peerCardBuilder, Key? key}) + {required this.peers, + required this.peerCardBuilder, + this.peerFilter, + Key? key}) : super(key: key); @override @@ -173,11 +178,33 @@ class _PeersViewState extends State<_PeersView> with WindowListener { } }(); } + + Future>? matchPeers(String searchText, List peers) async { + if (widget.peerFilter != null) { + peers = peers.where((peer) => widget.peerFilter!(peer)).toList(); + } + + searchText = searchText.trim(); + if (searchText.isEmpty) { + return peers; + } + searchText = searchText.toLowerCase(); + final matches = + await Future.wait(peers.map((peer) => matchPeer(searchText, peer))); + final filteredList = List.empty(growable: true); + for (var i = 0; i < peers.length; i++) { + if (matches[i]) { + filteredList.add(peers[i]); + } + } + return filteredList; + } } abstract class BasePeersView extends StatelessWidget { final String name; final String loadEvent; + final PeerFilter? peerFilter; final PeerCardBuilder peerCardBuilder; final List initPeers; @@ -185,6 +212,7 @@ abstract class BasePeersView extends StatelessWidget { Key? key, required this.name, required this.loadEvent, + this.peerFilter, required this.peerCardBuilder, required this.initPeers, }) : super(key: key); @@ -193,6 +221,7 @@ abstract class BasePeersView extends StatelessWidget { Widget build(BuildContext context) { return _PeersView( peers: Peers(name: name, loadEvent: loadEvent, peers: initPeers), + peerFilter: peerFilter, peerCardBuilder: peerCardBuilder); } } @@ -273,13 +302,12 @@ class AddressBookPeersView extends BasePeersView { key: key, name: 'address book peer', loadEvent: 'load_address_book_peers', - peerCardBuilder: (Peer peer) => Obx(() => Offstage( - key: ValueKey("off${peer.id}"), - offstage: !_hitTag(gFFI.abModel.selectedTags, peer.tags), - child: AddressBookPeerCard( - peer: peer, - menuPadding: menuPadding, - ))), + peerFilter: (Peer peer) => + _hitTag(gFFI.abModel.selectedTags, peer.tags), + peerCardBuilder: (Peer peer) => AddressBookPeerCard( + peer: peer, + menuPadding: menuPadding, + ), initPeers: initPeers, );