diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index 67b262cd1..a0a456807 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -11,6 +11,7 @@ import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; import 'package:flutter_hbb/models/ab_model.dart'; import 'package:flutter_hbb/models/platform_model.dart'; +import 'package:flutter_hbb/models/state_model.dart'; import 'package:url_launcher/url_launcher_string.dart'; import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; import 'package:get/get.dart'; @@ -61,15 +62,16 @@ class _AddressBookState extends State { retry: null, // remove retry close: () => gFFI.abModel.currentAbPushError.value = ''), Expanded( - child: (isDesktop || isWebDesktop) - ? _buildAddressBookDesktop() - : _buildAddressBookMobile()) + child: Obx(() => stateGlobal.isPortrait.isTrue + ? _buildAddressBookPortrait() + : _buildAddressBookLandscape()), + ), ], ); } }); - Widget _buildAddressBookDesktop() { + Widget _buildAddressBookLandscape() { return Row( children: [ Offstage( @@ -106,7 +108,7 @@ class _AddressBookState extends State { ); } - Widget _buildAddressBookMobile() { + Widget _buildAddressBookPortrait() { const padding = 8.0; return Column( children: [ @@ -239,14 +241,14 @@ class _AddressBookState extends State { bind.setLocalFlutterOption(k: kOptionCurrentAbName, v: value); } }, - customButton: Container( - height: isDesktop ? 48 : 40, + 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), @@ -335,8 +337,8 @@ class _AddressBookState extends State { showActionMenu: editPermission); } - final gridView = DynamicGridView.builder( - shrinkWrap: isMobile, + gridView(bool isPortrait) => DynamicGridView.builder( + shrinkWrap: isPortrait, gridDelegate: SliverGridDelegateWithWrapping(), itemCount: tags.length, itemBuilder: (BuildContext context, int index) { @@ -344,9 +346,9 @@ class _AddressBookState extends State { return tagBuilder(e); }); final maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0); - return (isDesktop || isWebDesktop) - ? gridView - : LimitedBox(maxHeight: maxHeight, child: gridView); + return Obx(() => stateGlobal.isPortrait.isFalse + ? gridView(false) + : LimitedBox(maxHeight: maxHeight, child: gridView(true))); }); } @@ -506,9 +508,9 @@ class _AddressBookState extends State { double marginBottom = 4; row({required Widget lable, required Widget input}) { - return Row( + makeChild(bool isPortrait) => Row( children: [ - !isMobile + !isPortrait ? ConstrainedBox( constraints: const BoxConstraints(minWidth: 100), child: lable.marginOnly(right: 10)) @@ -519,7 +521,8 @@ class _AddressBookState extends State { child: input), ), ], - ).marginOnly(bottom: !isMobile ? 8 : 0); + ).marginOnly(bottom: !isPortrait ? 8 : 0); + return Obx(() => makeChild(stateGlobal.isPortrait.isTrue)); } return CustomAlertDialog( @@ -542,24 +545,24 @@ class _AddressBookState extends State { ), ], ), - input: TextField( + input: Obx(() => TextField( controller: idController, inputFormatters: [IDTextInputFormatter()], decoration: InputDecoration( - labelText: !isMobile ? null : translate('ID'), + labelText: stateGlobal.isPortrait.isFalse ? null : translate('ID'), errorText: errorMsg, errorMaxLines: 5), - )), + ))), row( lable: Text( translate('Alias'), style: style, ), - input: TextField( + input: Obx(() => TextField( controller: aliasController, decoration: InputDecoration( - labelText: !isMobile ? null : translate('Alias'), - )), + labelText: stateGlobal.isPortrait.isFalse ? null : translate('Alias'), + ),)), ), if (isCurrentAbShared) row( @@ -567,11 +570,11 @@ class _AddressBookState extends State { translate('Password'), style: style, ), - input: TextField( + input: Obx(() => TextField( controller: passwordController, obscureText: !passwordVisible, decoration: InputDecoration( - labelText: !isMobile ? null : translate('Password'), + labelText: stateGlobal.isPortrait.isFalse ? null : translate('Password'), suffixIcon: IconButton( icon: Icon( passwordVisible @@ -585,7 +588,7 @@ class _AddressBookState extends State { }, ), ), - )), + ),)), if (gFFI.abModel.currentAbTags.isNotEmpty) Align( alignment: Alignment.centerLeft, diff --git a/flutter/lib/common/widgets/autocomplete.dart b/flutter/lib/common/widgets/autocomplete.dart index 07a11904d..978d053df 100644 --- a/flutter/lib/common/widgets/autocomplete.dart +++ b/flutter/lib/common/widgets/autocomplete.dart @@ -189,7 +189,7 @@ class AutocompletePeerTileState extends State { .map((e) => gFFI.abModel.getCurrentAbTagColor(e)) .toList(); return Tooltip( - message: isMobile + message: !(isDesktop || isWebDesktop) ? '' : widget.peer.tags.isNotEmpty ? '${translate('Tags')}: ${widget.peer.tags.join(', ')}' diff --git a/flutter/lib/common/widgets/dialog.dart b/flutter/lib/common/widgets/dialog.dart index 7cc76d6c6..5a72f5dc2 100644 --- a/flutter/lib/common/widgets/dialog.dart +++ b/flutter/lib/common/widgets/dialog.dart @@ -10,6 +10,7 @@ import 'package:flutter_hbb/common/widgets/setting_widgets.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/models/peer_model.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:qr_flutter/qr_flutter.dart'; @@ -1123,7 +1124,7 @@ void showRequestElevationDialog( errorText: errPwd.isEmpty ? null : errPwd.value, ), ], - ).marginOnly(left: (isDesktop || isWebDesktop) ? 35 : 0), + ).marginOnly(left: stateGlobal.isPortrait.isFalse ? 35 : 0), ).marginOnly(top: 10), ], ), diff --git a/flutter/lib/common/widgets/my_group.dart b/flutter/lib/common/widgets/my_group.dart index 0d9cc007c..2d26536eb 100644 --- a/flutter/lib/common/widgets/my_group.dart +++ b/flutter/lib/common/widgets/my_group.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/hbbs/hbbs.dart'; import 'package:flutter_hbb/common/widgets/login.dart'; import 'package:flutter_hbb/common/widgets/peers_view.dart'; +import 'package:flutter_hbb/models/state_model.dart'; import 'package:get/get.dart'; import '../../common.dart'; @@ -45,15 +46,15 @@ class _MyGroupState extends State { retry: null, close: () => gFFI.groupModel.groupLoadError.value = ''), Expanded( - child: (isDesktop || isWebDesktop) - ? _buildDesktop() - : _buildMobile()) + child: Obx(() => stateGlobal.isPortrait.isTrue + ? _buildPortrait() + : _buildLandscape())), ], ); }); } - Widget _buildDesktop() { + Widget _buildLandscape() { return Row( children: [ Container( @@ -89,7 +90,7 @@ class _MyGroupState extends State { ); } - Widget _buildMobile() { + Widget _buildPortrait() { return Column( children: [ Container( @@ -159,14 +160,14 @@ class _MyGroupState extends State { } return true; }).toList(); - final listView = ListView.builder( - shrinkWrap: isMobile, + listView(bool isPortrait) => ListView.builder( + shrinkWrap: isPortrait, itemCount: items.length, itemBuilder: (context, index) => _buildUserItem(items[index])); var maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0); - return (isDesktop || isWebDesktop) - ? listView - : LimitedBox(maxHeight: maxHeight, child: listView); + return Obx(() => stateGlobal.isPortrait.isFalse + ? listView(false) + : LimitedBox(maxHeight: maxHeight, child: listView(true))); }); } diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index f9dd73bbf..7760f7a03 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -4,6 +4,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_hbb/common/widgets/dialog.dart'; import 'package:flutter_hbb/consts.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'; @@ -53,14 +54,11 @@ class _PeerCardState extends State<_PeerCard> @override Widget build(BuildContext context) { super.build(context); - if (isDesktop || isWebDesktop) { - return _buildDesktop(); - } else { - return _buildMobile(); - } + return Obx(() => + stateGlobal.isPortrait.isTrue ? _buildPortrait() : _buildLandscape()); } - Widget _buildMobile() { + Widget _buildPortrait() { final peer = super.widget.peer; final PeerTabModel peerTabModel = Provider.of(context); return Card( @@ -87,7 +85,7 @@ class _PeerCardState extends State<_PeerCard> )); } - Widget _buildDesktop() { + Widget _buildLandscape() { final PeerTabModel peerTabModel = Provider.of(context); final peer = super.widget.peer; var deco = Rx( @@ -140,13 +138,13 @@ class _PeerCardState extends State<_PeerCard> final greyStyle = TextStyle( fontSize: 11, color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6)); - final child = Row( + makeChild(bool isPortrait) => Row( mainAxisSize: MainAxisSize.max, children: [ Container( decoration: BoxDecoration( color: str2color('${peer.id}${peer.platform}', 0x7f), - borderRadius: isMobile + borderRadius: isPortrait ? BorderRadius.circular(_tileRadius) : BorderRadius.only( topLeft: Radius.circular(_tileRadius), @@ -154,11 +152,11 @@ class _PeerCardState extends State<_PeerCard> ), ), alignment: Alignment.center, - width: isMobile ? 50 : 42, - height: isMobile ? 50 : null, + width: isPortrait ? 50 : 42, + height: isPortrait ? 50 : null, child: Stack( children: [ - getPlatformImage(peer.platform, size: isMobile ? 38 : 30) + getPlatformImage(peer.platform, size: isPortrait ? 38 : 30) .paddingAll(6), if (_shouldBuildPasswordIcon(peer)) Positioned( @@ -183,19 +181,19 @@ class _PeerCardState extends State<_PeerCard> child: Column( children: [ Row(children: [ - getOnline(isMobile ? 4 : 8, peer.online), + getOnline(isPortrait ? 4 : 8, peer.online), Expanded( child: Text( peer.alias.isEmpty ? formatID(peer.id) : peer.alias, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.titleSmall, )), - ]).marginOnly(top: isMobile ? 0 : 2), + ]).marginOnly(top: isPortrait ? 0 : 2), Align( alignment: Alignment.centerLeft, child: Text( name, - style: isMobile ? null : greyStyle, + style: isPortrait ? null : greyStyle, textAlign: TextAlign.start, overflow: TextOverflow.ellipsis, ), @@ -203,9 +201,9 @@ class _PeerCardState extends State<_PeerCard> ], ).marginOnly(top: 2), ), - isMobile - ? checkBoxOrActionMoreMobile(peer) - : checkBoxOrActionMoreDesktop(peer, isTile: true), + isPortrait + ? checkBoxOrActionMorePortrait(peer) + : checkBoxOrActionMoreLandscape(peer, isTile: true), ], ).paddingOnly(left: 10.0, top: 3.0), ), @@ -216,28 +214,27 @@ class _PeerCardState extends State<_PeerCard> .map((e) => gFFI.abModel.getCurrentAbTagColor(e)) .toList(); return Tooltip( - message: isMobile + message: !(isDesktop || isWebDesktop) ? '' : peer.tags.isNotEmpty ? '${translate('Tags')}: ${peer.tags.join(', ')}' : '', child: Stack(children: [ - deco == null - ? child - : Obx( - () => Container( + Obx(() => deco == null + ? makeChild(stateGlobal.isPortrait.isTrue) + : Container( foregroundDecoration: deco.value, - child: child, + child: makeChild(stateGlobal.isPortrait.isTrue), ), ), if (colors.isNotEmpty) - Positioned( + Obx(()=> Positioned( top: 2, - right: isMobile ? 20 : 10, + right: stateGlobal.isPortrait.isTrue ? 20 : 10, child: CustomPaint( painter: TagPainter(radius: 3, colors: colors), ), - ) + )) ]), ); } @@ -316,7 +313,7 @@ class _PeerCardState extends State<_PeerCard> style: Theme.of(context).textTheme.titleSmall, )), ]).paddingSymmetric(vertical: 8)), - checkBoxOrActionMoreDesktop(peer, isTile: false), + checkBoxOrActionMoreLandscape(peer, isTile: false), ], ).paddingSymmetric(horizontal: 12.0), ) @@ -362,7 +359,7 @@ class _PeerCardState extends State<_PeerCard> } } - Widget checkBoxOrActionMoreMobile(Peer peer) { + Widget checkBoxOrActionMorePortrait(Peer peer) { final PeerTabModel peerTabModel = Provider.of(context); final selected = peerTabModel.isPeerSelected(peer.id); if (peerTabModel.multiSelectionMode) { @@ -390,7 +387,7 @@ class _PeerCardState extends State<_PeerCard> } } - Widget checkBoxOrActionMoreDesktop(Peer peer, {required bool isTile}) { + Widget checkBoxOrActionMoreLandscape(Peer peer, {required bool isTile}) { final PeerTabModel peerTabModel = Provider.of(context); final selected = peerTabModel.isPeerSelected(peer.id); if (peerTabModel.multiSelectionMode) { @@ -1257,9 +1254,9 @@ void _rdpDialog(String id) async { ), ], ).marginOnly(bottom: isDesktop ? 8 : 0), - Row( + Obx(() => Row( children: [ - (isDesktop || isWebDesktop) + stateGlobal.isPortrait.isFalse ? ConstrainedBox( constraints: const BoxConstraints(minWidth: 140), child: Text( @@ -1270,17 +1267,17 @@ void _rdpDialog(String id) async { Expanded( child: TextField( decoration: InputDecoration( - labelText: (isDesktop || isWebDesktop) + labelText: isDesktop ? null : translate('Username')), controller: userController, ), ), ], - ).marginOnly(bottom: (isDesktop || isWebDesktop) ? 8 : 0), - Row( + ).marginOnly(bottom: stateGlobal.isPortrait.isFalse ? 8 : 0)), + Obx(() => Row( children: [ - (isDesktop || isWebDesktop) + stateGlobal.isPortrait.isFalse ? ConstrainedBox( constraints: const BoxConstraints(minWidth: 140), child: Text( @@ -1292,7 +1289,7 @@ void _rdpDialog(String id) async { child: Obx(() => TextField( obscureText: secure.value, decoration: InputDecoration( - labelText: (isDesktop || isWebDesktop) + labelText: isDesktop ? null : translate('Password'), suffixIcon: IconButton( @@ -1304,7 +1301,7 @@ void _rdpDialog(String id) async { )), ), ], - ) + )) ], ), ), diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index 8fe731449..c941a1b93 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -16,6 +16,7 @@ import 'package:flutter_hbb/models/ab_model.dart'; import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/models/peer_tab_model.dart'; +import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; @@ -114,26 +115,26 @@ class _PeerTabPageState extends State textBaseline: TextBaseline.ideographic, crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - height: 32, - child: Container( - padding: (isDesktop || isWebDesktop) - ? null - : EdgeInsets.symmetric(horizontal: 2), - child: selectionWrap(Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: - visibleContextMenuListener(_createSwitchBar(context))), - if (isMobile) - ..._mobileRightActions(context) - else - ..._desktopRightActions(context) - ], - )), - ), - ).paddingOnly(right: (isDesktop || isWebDesktop) ? 12 : 0), + Obx(() => SizedBox( + height: 32, + child: Container( + padding: stateGlobal.isPortrait.isTrue + ? EdgeInsets.symmetric(horizontal: 2) + : null, + child: selectionWrap(Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: visibleContextMenuListener( + _createSwitchBar(context))), + if (stateGlobal.isPortrait.isTrue) + ..._portraitRightActions(context) + else + ..._landscapeRightActions(context) + ], + )), + ), + ).paddingOnly(right: stateGlobal.isPortrait.isTrue ? 0 : 12)), _createPeersView(), ], ); @@ -299,7 +300,7 @@ class _PeerTabPageState extends State } Widget visibleContextMenuListener(Widget child) { - if (isMobile) { + if (!(isDesktop || isWebDesktop)) { return GestureDetector( onLongPressDown: (e) { final x = e.globalPosition.dx; @@ -456,7 +457,7 @@ class _PeerTabPageState extends State showToast(translate('Successful')); }, child: Icon(PeerTabModel.icons[PeerTabIndex.fav.index]), - ).marginOnly(left: isMobile ? 11 : 6), + ).marginOnly(left: !(isDesktop || isWebDesktop) ? 11 : 6), ); } @@ -477,7 +478,7 @@ class _PeerTabPageState extends State model.setMultiSelectionMode(false); }, child: Icon(PeerTabModel.icons[PeerTabIndex.ab.index]), - ).marginOnly(left: isMobile ? 11 : 6), + ).marginOnly(left: !(isDesktop || isWebDesktop) ? 11 : 6), ); } @@ -500,7 +501,7 @@ class _PeerTabPageState extends State }); }, child: Icon(Icons.tag)) - .marginOnly(left: isMobile ? 11 : 6), + .marginOnly(left: !(isDesktop || isWebDesktop) ? 11 : 6), ); } @@ -556,10 +557,10 @@ class _PeerTabPageState extends State }); } - List _desktopRightActions(BuildContext context) { + List _landscapeRightActions(BuildContext context) { final model = Provider.of(context); return [ - const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13), + const PeerSearchBar().marginOnly(right: 13), _createRefresh( index: PeerTabIndex.ab, loading: gFFI.abModel.currentAbLoading), _createRefresh( @@ -580,7 +581,7 @@ class _PeerTabPageState extends State ]; } - List _mobileRightActions(BuildContext context) { + List _portraitRightActions(BuildContext context) { final model = Provider.of(context); final screenWidth = MediaQuery.of(context).size.width; final leftIconSize = Theme.of(context).iconTheme.size ?? 24; @@ -701,13 +702,13 @@ class _PeerSearchBarState extends State { baseOffset: 0, extentOffset: peerSearchTextController.value.text.length); }); - return Container( - width: isMobile ? 120 : 140, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.circular(6), - ), - child: Obx(() => Row( + return Obx(() => Container( + width: stateGlobal.isPortrait.isTrue ? 120 : 140, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(6), + ), + child: Row( children: [ Expanded( child: Row( @@ -768,8 +769,8 @@ class _PeerSearchBarState extends State { ), ) ], - )), - ); + ), + )); } } diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index ef9647eaa..befc73338 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/state_model.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:visibility_detector/visibility_detector.dart'; @@ -128,7 +129,7 @@ class _PeersViewState extends State<_PeersView> @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); - if (isDesktop) return; + if (isDesktop || isWebDesktop) return; if (state == AppLifecycleState.resumed) { _isActive = true; _queryCount = 0; @@ -194,7 +195,7 @@ class _PeersViewState extends State<_PeersView> var peers = snapshot.data!; if (peers.length > 1000) peers = peers.sublist(0, 1000); gFFI.peerTabModel.setCurrentTabCachedPeers(peers); - buildOnePeer(Peer peer) { + buildOnePeer(Peer peer, bool isPortrait) { final visibilityChild = VisibilityDetector( key: ValueKey(_cardId(peer.id)), onVisibilityChanged: onVisibilityChanged, @@ -206,7 +207,7 @@ class _PeersViewState extends State<_PeersView> // No need to listen the currentTab change event. // Because the currentTab change event will trigger the peers change event, // and the peers change event will trigger _buildPeersView(). - return (isDesktop || isWebDesktop) + return !isPortrait ? Obx(() => peerCardUiType.value == PeerUiType.list ? Container(height: 45, child: visibilityChild) : peerCardUiType.value == PeerUiType.grid @@ -217,44 +218,41 @@ class _PeersViewState extends State<_PeersView> : Container(child: visibilityChild); } - final Widget child; - if (isMobile) { - child = ListView.builder( - itemCount: peers.length, - itemBuilder: (BuildContext context, int index) { - return buildOnePeer(peers[index]).marginOnly( - top: index == 0 ? 0 : space / 2, bottom: space / 2); - }, - ); - } else { - child = Obx(() => peerCardUiType.value == PeerUiType.list - ? DesktopScrollWrapper( - scrollController: _scrollController, - child: ListView.builder( - controller: _scrollController, - physics: DraggableNeverScrollableScrollPhysics(), - itemCount: peers.length, - itemBuilder: (BuildContext context, int index) { - return buildOnePeer(peers[index]).marginOnly( - right: space, - top: index == 0 ? 0 : space / 2, - bottom: space / 2); - }), - ) - : DesktopScrollWrapper( - scrollController: _scrollController, - child: DynamicGridView.builder( - controller: _scrollController, - physics: DraggableNeverScrollableScrollPhysics(), - gridDelegate: SliverGridDelegateWithWrapping( - mainAxisSpacing: space / 2, - crossAxisSpacing: space), - itemCount: peers.length, - itemBuilder: (BuildContext context, int index) { - return buildOnePeer(peers[index]); - }), - )); - } + final Widget child = Obx(() => stateGlobal.isPortrait.isTrue + ? ListView.builder( + itemCount: peers.length, + itemBuilder: (BuildContext context, int index) { + return buildOnePeer(peers[index], true).marginOnly( + top: index == 0 ? 0 : space / 2, bottom: space / 2); + }, + ) + : peerCardUiType.value == PeerUiType.list + ? DesktopScrollWrapper( + scrollController: _scrollController, + child: ListView.builder( + controller: _scrollController, + physics: DraggableNeverScrollableScrollPhysics(), + itemCount: peers.length, + itemBuilder: (BuildContext context, int index) { + return buildOnePeer(peers[index], false).marginOnly( + right: space, + top: index == 0 ? 0 : space / 2, + bottom: space / 2); + }), + ) + : DesktopScrollWrapper( + scrollController: _scrollController, + child: DynamicGridView.builder( + controller: _scrollController, + physics: DraggableNeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithWrapping( + mainAxisSpacing: space / 2, + crossAxisSpacing: space), + itemCount: peers.length, + itemBuilder: (BuildContext context, int index) { + return buildOnePeer(peers[index], false); + }), + )); if (updateEvent == UpdateEvent.load) { _curPeers.clear(); diff --git a/flutter/lib/common/widgets/remote_input.dart b/flutter/lib/common/widgets/remote_input.dart index fb19c4c23..a4d3caf29 100644 --- a/flutter/lib/common/widgets/remote_input.dart +++ b/flutter/lib/common/widgets/remote_input.dart @@ -243,7 +243,7 @@ class _RawTouchGestureDetectorRegionState if (ffi.cursorModel.shouldBlock(d.localPosition.dx, d.localPosition.dy)) { return; } - if (isDesktop) { + if (isDesktop || isWebDesktop) { ffi.cursorModel.trySetRemoteWindowCoords(); } // Workaround for the issue that the first pan event is sent a long time after the start event. @@ -285,7 +285,7 @@ class _RawTouchGestureDetectorRegionState if (lastDeviceKind != PointerDeviceKind.touch) { return; } - if (isDesktop) { + if (isDesktop || isWebDesktop) { ffi.cursorModel.clearRemoteWindowCoords(); } inputModel.sendMouse('up', MouseButtons.left); diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index c2009bcae..dc02ac81f 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -372,7 +372,7 @@ class App extends StatefulWidget { State createState() => _AppState(); } -class _AppState extends State { +class _AppState extends State with WidgetsBindingObserver { @override void initState() { super.initState(); @@ -396,6 +396,34 @@ class _AppState extends State { bind.mainChangeTheme(dark: to.toShortString()); } }; + WidgetsBinding.instance.addObserver(this); + WidgetsBinding.instance.addPostFrameCallback((_) => _updateOrientation()); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeMetrics() { + _updateOrientation(); + } + + void _updateOrientation() { + if (isDesktop) return; + + // Don't use `MediaQuery.of(context).orientation` in `didChangeMetrics()`, + // my test (Flutter 3.19.6, Android 14) is always the reverse value. + // https://github.com/flutter/flutter/issues/60899 + // stateGlobal.isPortrait.value = + // MediaQuery.of(context).orientation == Orientation.portrait; + + final orientation = View.of(context).physicalSize.aspectRatio > 1 + ? Orientation.landscape + : Orientation.portrait; + stateGlobal.isPortrait.value = orientation == Orientation.portrait; } @override diff --git a/flutter/lib/models/state_model.dart b/flutter/lib/models/state_model.dart index 7c4d3cfd0..e18874785 100644 --- a/flutter/lib/models/state_model.dart +++ b/flutter/lib/models/state_model.dart @@ -20,6 +20,8 @@ class StateGlobal { final svcStatus = SvcStatus.notReady.obs; final RxBool isFocused = false.obs; + final isPortrait = false.obs; + String _inputSource = ''; // Use for desktop -> remote toolbar -> resolution