From cdb264d47b264f4ceb7f13651e05f302bb56ca3c Mon Sep 17 00:00:00 2001 From: 21pages Date: Sat, 19 Aug 2023 08:20:48 +0800 Subject: [PATCH 1/4] opt ui: hover decoration of peer tab icon, add tile card checkbox margin Signed-off-by: 21pages --- flutter/lib/common/widgets/peer_card.dart | 11 +- flutter/lib/common/widgets/peer_tab_page.dart | 248 ++++++++++++------ 2 files changed, 167 insertions(+), 92 deletions(-) diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 71b63d975..b5f876d2e 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -222,7 +222,7 @@ class _PeerCardState extends State<_PeerCard> ], ).marginOnly(top: 2), ), - checkBoxOrActionMoreDesktop(peer), + checkBoxOrActionMoreDesktop(peer, isTile: true), ], ).paddingOnly(left: 10.0, top: 3.0), ), @@ -321,7 +321,7 @@ class _PeerCardState extends State<_PeerCard> style: Theme.of(context).textTheme.titleSmall, )), ]).paddingSymmetric(vertical: 8)), - checkBoxOrActionMoreDesktop(peer), + checkBoxOrActionMoreDesktop(peer, isTile: false), ], ).paddingSymmetric(horizontal: 12.0), ) @@ -387,7 +387,7 @@ class _PeerCardState extends State<_PeerCard> } } - Widget checkBoxOrActionMoreDesktop(Peer peer) { + Widget checkBoxOrActionMoreDesktop(Peer peer, {required bool isTile}) { final PeerTabModel peerTabModel = Provider.of(context); final selected = peerTabModel.isPeerSelected(peer.id); if (peerTabModel.multiSelectionMode) { @@ -398,14 +398,15 @@ class _PeerCardState extends State<_PeerCard> ) : Icon(Icons.check_box_outline_blank); bool last = peerTabModel.isShiftDown && peer.id == peerTabModel.lastId; + double right = isTile ? 4 : 0; if (last) { return Container( decoration: BoxDecoration( border: Border.all(color: MyTheme.accent, width: 1)), child: icon, - ); + ).marginOnly(right: right); } else { - return icon; + return icon.marginOnly(right: right); } } else { return _actionMore(peer); diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index b4c9b4018..30b67abbe 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -111,31 +111,26 @@ class _PeerTabPageState extends State child: _createPeerViewTypeSwitch(context)), Offstage( offstage: gFFI.peerTabModel.currentTab == 0, - child: PeerSortDropdown().marginOnly(left: 8), + child: PeerSortDropdown(), ), Offstage( offstage: gFFI.peerTabModel.currentTab != 3, - child: InkWell( - child: Obx(() => Container( - padding: EdgeInsets.all(4.0), - decoration: hideAbTagsPanel.value - ? null - : BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.circular(6)), - child: Tooltip( - message: translate('Toggle Tags'), - child: Icon( - Icons.tag_rounded, - size: 18, - )))), + child: _hoverAction( + context: context, + hoverableWhenfalse: hideAbTagsPanel, + child: Tooltip( + message: translate('Toggle Tags'), + child: Icon( + Icons.tag_rounded, + size: 18, + )), onTap: () async { await bind.mainSetLocalOption( key: "hideAbTagsPanel", value: hideAbTagsPanel.value ? "" : "Y"); hideAbTagsPanel.value = !hideAbTagsPanel.value; }, - ).marginOnly(left: 8), + ), ), ], )), @@ -211,8 +206,7 @@ class _PeerTabPageState extends State if (gFFI.peerTabModel.currentTab < 3) return Offstage(); final textColor = Theme.of(context).textTheme.titleLarge?.color; return Container( - padding: EdgeInsets.all(4.0), - child: AnimatedRotationWidget( + child: RefreshWidget( onPressed: () { if (gFFI.peerTabModel.currentTab < entries.length) { entries[gFFI.peerTabModel.currentTab].load(); @@ -234,57 +228,45 @@ class _PeerTabPageState extends State Widget _createPeerViewTypeSwitch(BuildContext context) { final textColor = Theme.of(context).textTheme.titleLarge?.color; final types = [PeerUiType.grid, PeerUiType.list]; - final hover = false.obs; - final deco = BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.circular(5), - ); - return Obx( - () => Container( - padding: EdgeInsets.all(4.0), - decoration: hover.value ? deco : null, - child: InkWell( - onHover: (value) => hover.value = value, - onTap: () async { - final type = types.elementAt( - peerCardUiType.value == types.elementAt(0) ? 1 : 0); - await bind.setLocalFlutterOption( - k: 'peer-card-ui-type', v: type.index.toString()); - peerCardUiType.value = type; - }, - child: Tooltip( - message: peerCardUiType.value == PeerUiType.grid - ? translate('List View') - : translate('Grid View'), - child: Icon( - peerCardUiType.value == PeerUiType.grid - ? Icons.view_list_rounded - : Icons.grid_view_rounded, - size: 18, - color: textColor, - )))), - ); + return Obx(() => _hoverAction( + context: context, + onTap: () async { + final type = types + .elementAt(peerCardUiType.value == types.elementAt(0) ? 1 : 0); + await bind.setLocalFlutterOption( + k: 'peer-card-ui-type', v: type.index.toString()); + peerCardUiType.value = type; + }, + child: Tooltip( + message: peerCardUiType.value == PeerUiType.grid + ? translate('List View') + : translate('Grid View'), + child: Icon( + peerCardUiType.value == PeerUiType.grid + ? Icons.view_list_rounded + : Icons.grid_view_rounded, + size: 18, + color: textColor, + )))); } Widget _createMultiSelection() { final textColor = Theme.of(context).textTheme.titleLarge?.color; final model = Provider.of(context); if (model.currentTabCachedPeers.isEmpty) return Offstage(); - return Container( - padding: EdgeInsets.all(4.0), - child: InkWell( - onTap: () { - model.setMultiSelectionMode(true); - }, - child: Tooltip( - message: translate('Select'), - child: Icon( - IconFont.checkbox, - size: 18, - color: textColor, - )), - ), + return _hoverAction( + context: context, + onTap: () { + model.setMultiSelectionMode(true); + }, + child: Tooltip( + message: translate('Select'), + child: Icon( + IconFont.checkbox, + size: 18, + color: textColor, + )), ); } @@ -306,7 +288,8 @@ class _PeerTabPageState extends State Widget deleteSelection() { final model = Provider.of(context); - return InkWell( + return _hoverAction( + context: context, onTap: () { onSubmit() async { final peers = model.selectedPeers; @@ -367,7 +350,8 @@ class _PeerTabPageState extends State return Offstage( offstage: model.currentTab != PeerTabIndex.recent.index, // show based on recent - child: InkWell( + child: _hoverAction( + context: context, onTap: () async { final peers = model.selectedPeers; final favs = (await bind.mainGetFav()).toList(); @@ -381,10 +365,9 @@ class _PeerTabPageState extends State showToast(translate('Successful')); }, child: Tooltip( - message: translate('Add to Favorites'), - child: Icon(model.icons[PeerTabIndex.fav.index])) - .marginOnly(left: isMobile ? 15 : 10), - ), + message: translate('Add to Favorites'), + child: Icon(model.icons[PeerTabIndex.fav.index])), + ).marginOnly(left: isMobile ? 11 : 6), ); } @@ -393,7 +376,8 @@ class _PeerTabPageState extends State return Offstage( offstage: !gFFI.userModel.isLogin || model.currentTab == PeerTabIndex.ab.index, - child: InkWell( + child: _hoverAction( + context: context, onTap: () { if (gFFI.abModel.isFull(true)) { return; @@ -409,10 +393,9 @@ class _PeerTabPageState extends State }); }, child: Tooltip( - message: translate('Add to Address Book'), - child: Icon(model.icons[PeerTabIndex.ab.index])) - .marginOnly(left: isMobile ? 15 : 10), - ), + message: translate('Add to Address Book'), + child: Icon(model.icons[PeerTabIndex.ab.index])), + ).marginOnly(left: isMobile ? 11 : 6), ); } @@ -422,7 +405,8 @@ class _PeerTabPageState extends State offstage: !gFFI.userModel.isLogin || model.currentTab != PeerTabIndex.ab.index || gFFI.abModel.tags.isEmpty, - child: InkWell( + child: _hoverAction( + context: context, onTap: () { editAbTagDialog(List.empty(), (selectedTags) async { final peers = model.selectedPeers; @@ -435,7 +419,7 @@ class _PeerTabPageState extends State }, child: Tooltip( message: translate('Edit Tag'), child: Icon(Icons.tag))) - .marginOnly(left: isMobile ? 15 : 10), + .marginOnly(left: isMobile ? 11 : 6), ); } @@ -451,26 +435,27 @@ class _PeerTabPageState extends State return Offstage( offstage: model.selectedPeers.length >= model.currentTabCachedPeers.length, - child: InkWell( + child: _hoverAction( + context: context, onTap: () { model.selectAll(); }, child: Tooltip( - message: translate('Select All'), child: Icon(Icons.select_all)) - .marginOnly(left: 10), - ), + message: translate('Select All'), child: Icon(Icons.select_all)), + ).marginOnly(left: 6), ); } Widget closeSelection() { final model = Provider.of(context); - return InkWell( + return _hoverAction( + context: context, onTap: () { model.setMultiSelectionMode(false); }, child: Tooltip(message: translate('Close'), child: Icon(Icons.clear))) - .marginOnly(left: 10); + .marginOnly(left: 6); } } @@ -488,15 +473,15 @@ class _PeerSearchBarState extends State { Widget build(BuildContext context) { return drawer ? _buildSearchBar() - : IconButton( - alignment: Alignment.centerRight, + : _hoverAction( + context: context, padding: const EdgeInsets.only(right: 2), - onPressed: () { + onTap: () { setState(() { drawer = true; }); }, - icon: Tooltip( + child: Tooltip( message: translate('Search'), child: Icon( Icons.search_rounded, @@ -514,7 +499,7 @@ class _PeerSearchBarState extends State { extentOffset: peerSearchTextController.value.text.length); }); return Container( - width: 120, + width: isMobile ? 120 : 140, decoration: BoxDecoration( color: Theme.of(context).colorScheme.background, borderRadius: BorderRadius.circular(6), @@ -638,7 +623,8 @@ class _PeerSortDropdownState extends State { } var menuPos = RelativeRect.fromLTRB(0, 0, 0, 0); - return InkWell( + return _hoverAction( + context: context, child: Tooltip( message: translate('Sort by'), child: Icon( @@ -659,3 +645,91 @@ class _PeerSortDropdownState extends State { ); } } + +class RefreshWidget extends StatefulWidget { + final VoidCallback onPressed; + final Widget child; + final RxBool? spinning; + const RefreshWidget( + {super.key, required this.onPressed, required this.child, this.spinning}); + + @override + State createState() => RefreshWidgetState(); +} + +class RefreshWidgetState extends State { + double turns = 0.0; + bool hover = false; + + @override + void initState() { + super.initState(); + widget.spinning?.listen((v) { + if (v && mounted) { + setState(() { + turns += 1; + }); + } + }); + } + + @override + Widget build(BuildContext context) { + final deco = BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(6), + ); + return AnimatedRotation( + turns: turns, + duration: const Duration(milliseconds: 200), + onEnd: () { + if (widget.spinning?.value == true && mounted) { + setState(() => turns += 1.0); + } + }, + child: Container( + padding: EdgeInsets.all(4.0), + margin: EdgeInsets.symmetric(horizontal: 1), + decoration: hover ? deco : null, + child: InkWell( + onTap: () { + if (mounted) setState(() => turns += 1.0); + widget.onPressed(); + }, + onHover: (value) { + if (mounted) { + setState(() { + hover = value; + }); + } + }, + child: widget.child), + )); + } +} + +Widget _hoverAction( + {required BuildContext context, + required Widget child, + required Function() onTap, + GestureTapDownCallback? onTapDown, + RxBool? hoverableWhenfalse, + EdgeInsetsGeometry padding = const EdgeInsets.all(4.0)}) { + final hover = false.obs; + final deco = BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(6), + ); + return Obx( + () => Container( + padding: padding, + margin: EdgeInsets.symmetric(horizontal: 1), + decoration: + (hover.value || hoverableWhenfalse?.value == false) ? deco : null, + child: InkWell( + onHover: (value) => hover.value = value, + onTap: onTap, + onTapDown: onTapDown, + child: child)), + ); +} From df02292338d6a0a9406b094cea4cd0f3eae62529 Mon Sep 17 00:00:00 2001 From: 21pages Date: Sat, 19 Aug 2023 15:17:13 +0800 Subject: [PATCH 2/4] restore online status after pullAb Signed-off-by: 21pages --- flutter/lib/models/ab_model.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index 491dae33b..03e08434f 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -93,6 +93,8 @@ class AbModel { } catch (e) {} final data = jsonDecode(json['data']); if (data != null) { + final oldOnlineIDs = + peers.where((e) => e.online).map((e) => e.id).toList(); tags.clear(); peers.clear(); if (data['tags'] is List) { @@ -106,6 +108,11 @@ class AbModel { if (isFull(false)) { peers.removeRange(licensedDevices, peers.length); } + // restore online + peers + .where((e) => oldOnlineIDs.contains(e.id)) + .map((e) => e.online = true) + .toList(); _saveCache(); // save on success } } From f68ef1492f30030a389517d5b50123231c0036dc Mon Sep 17 00:00:00 2001 From: RustDesk <71636191+rustdesk@users.noreply.github.com> Date: Sat, 19 Aug 2023 17:41:28 +0800 Subject: [PATCH 3/4] Update flutter-build.yml --- .github/workflows/flutter-build.yml | 81 +++++++++++++++-------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index 1415cc79d..2c1e2ad49 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -617,7 +617,7 @@ jobs: build-rustdesk-lib-linux-amd64: needs: [generate-bridge-linux, build-vcpkg-deps-linux] name: build-rust-lib ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}] - runs-on: ${{ matrix.job.os }} + runs-on: [self-hosted] strategy: fail-fast: false matrix: @@ -648,25 +648,26 @@ jobs: } # - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } steps: - - name: Maximize build space - run: | - sudo rm -rf /opt/ghc - sudo rm -rf /usr/local/lib/android - sudo rm -rf /usr/share/dotnet - sudo apt update -y - sudo apt install qemu-user-static + #- name: Maximize build space + # run: | + # sudo rm -rf /opt/ghc + # sudo rm -rf /usr/local/lib/android + # sudo rm -rf /usr/share/dotnet + # sudo apt update -y + # sudo apt install qemu-user-static -y - name: Checkout source code uses: actions/checkout@v3 - - name: Set Swap Space - uses: pierotofy/set-swap-space@master - with: - swap-size-gb: 12 + #- name: Set Swap Space + # uses: pierotofy/set-swap-space@master + # with: + # swap-size-gb: 12 - name: Free Space run: | - df + df -h + free -m - name: Install Rust toolchain uses: actions-rs/toolchain@v1 @@ -765,7 +766,7 @@ jobs: if: ${{ inputs.upload-artifact }} needs: [generate-bridge-linux, build-vcpkg-deps-linux] name: build-rust-lib ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}] - runs-on: ${{ matrix.job.os }} + runs-on: [self-hosted] strategy: fail-fast: false matrix: @@ -798,25 +799,26 @@ jobs: # - { arch: armv7, target: armv7-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true, extra-build-features: "appimage" } # - { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true } steps: - - name: Maximize build space - run: | - sudo rm -rf /opt/ghc - sudo rm -rf /usr/local/lib/android - sudo rm -rf /usr/share/dotnet - sudo apt update -y - sudo apt install qemu-user-static + #- name: Maximize build space + # run: | + # sudo rm -rf /opt/ghc + # sudo rm -rf /usr/local/lib/android + # sudo rm -rf /usr/share/dotnet + # sudo apt update -y + # sudo apt install qemu-user-static -y - name: Checkout source code uses: actions/checkout@v3 - - name: Set Swap Space - uses: pierotofy/set-swap-space@master - with: - swap-size-gb: 12 + #- name: Set Swap Space + # uses: pierotofy/set-swap-space@master + # with: + # swap-size-gb: 12 - name: Free Space run: | - df + df -h + free -m - name: Install Rust toolchain uses: actions-rs/toolchain@v1 @@ -908,7 +910,7 @@ jobs: if: ${{ inputs.upload-artifact }} needs: [build-vcpkg-deps-linux] name: build-rustdesk(sciter) ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}] - runs-on: ${{ matrix.job.os }} + runs-on: [self-hosted] strategy: fail-fast: false matrix: @@ -927,25 +929,26 @@ jobs: # - { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true } steps: - - name: Maximize build space - run: | - sudo rm -rf /opt/ghc - sudo rm -rf /usr/local/lib/android - sudo rm -rf /usr/share/dotnet - sudo apt update -y - sudo apt install qemu-user-static + #- name: Maximize build space + # run: | + # sudo rm -rf /opt/ghc + # sudo rm -rf /usr/local/lib/android + # sudo rm -rf /usr/share/dotnet + # sudo apt update -y + # sudo apt install qemu-user-static -y - name: Checkout source code uses: actions/checkout@v3 - - name: Set Swap Space - uses: pierotofy/set-swap-space@master - with: - swap-size-gb: 12 + #- name: Set Swap Space + # uses: pierotofy/set-swap-space@master + # with: + # swap-size-gb: 12 - name: Free Space run: | - df + df -h + free -m - name: Install Rust toolchain uses: actions-rs/toolchain@v1 From 9cba0a0b89930e7c131c78fe46b56f93da4a1819 Mon Sep 17 00:00:00 2001 From: 21pages Date: Sat, 19 Aug 2023 18:12:03 +0800 Subject: [PATCH 4/4] fix refresh icon stops after switching tab Signed-off-by: 21pages --- flutter/lib/common/widgets/peer_tab_page.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index 30b67abbe..259a59300 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -203,9 +203,9 @@ class _PeerTabPageState extends State } Widget _createRefresh() { - if (gFFI.peerTabModel.currentTab < 3) return Offstage(); final textColor = Theme.of(context).textTheme.titleLarge?.color; - return Container( + return Offstage( + offstage: gFFI.peerTabModel.currentTab != PeerTabIndex.ab.index, child: RefreshWidget( onPressed: () { if (gFFI.peerTabModel.currentTab < entries.length) {