From 40d0ea016bae6dca3b51f503f25003312f129299 Mon Sep 17 00:00:00 2001 From: 21pages Date: Fri, 3 Feb 2023 15:07:45 +0800 Subject: [PATCH] refactor peer tab with model, make it scrollable Signed-off-by: 21pages --- flutter/lib/common/widgets/peer_tab_page.dart | 449 ++++++------------ flutter/lib/main.dart | 1 + flutter/lib/mobile/widgets/dialog.dart | 6 +- flutter/lib/models/group_model.dart | 4 +- flutter/lib/models/model.dart | 5 +- flutter/lib/models/peer_tab_model.dart | 275 +++++++++++ flutter/lib/models/user_model.dart | 2 +- 7 files changed, 423 insertions(+), 319 deletions(-) create mode 100644 flutter/lib/models/peer_tab_model.dart diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart index 150121c59..4080f9c11 100644 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ b/flutter/lib/common/widgets/peer_tab_page.dart @@ -1,7 +1,7 @@ -import 'dart:convert'; import 'dart:ui' as ui; import 'package:bot_toast/bot_toast.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/widgets/address_book.dart'; import 'package:flutter_hbb/common/widgets/my_group.dart'; @@ -12,181 +12,15 @@ import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart' as mod_menu; +import 'package:flutter_hbb/models/peer_tab_model.dart'; import 'package:get/get.dart'; +import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart'; +import 'package:provider/provider.dart'; +import 'package:visibility_detector/visibility_detector.dart'; import '../../common.dart'; import '../../models/platform_model.dart'; -const int groupTabIndex = 4; -const String defaultGroupTabname = 'Group'; - -class StatePeerTab { - final RxInt currentTab = 0.obs; // index in tabNames - final RxList visibleOrderedTabs = RxList.empty(growable: true); - List tabOrder = List.from([0, 1, 2, 3, 4]); // constant length - final RxInt tabHiddenFlag = 0.obs; - final RxList tabNames = [ - 'Recent Sessions', - 'Favorites', - 'Discovered', - 'Address Book', - defaultGroupTabname, - ].obs; - - StatePeerTab._() { - // init tabHiddenFlag - tabHiddenFlag.value = (int.tryParse( - bind.getLocalFlutterConfig(k: 'hidden-peer-card'), - radix: 2) ?? - 0); - var tabs = _notHiddenTabs(); - // remove dynamic tabs - tabs.remove(groupTabIndex); - // init tabOrder - try { - final conf = bind.getLocalFlutterConfig(k: 'peer-tab-order'); - if (conf.isNotEmpty) { - final json = jsonDecode(conf); - if (json is List) { - final List list = - json.map((e) => int.tryParse(e.toString()) ?? -1).toList(); - if (list.length == tabOrder.length && - tabOrder.every((e) => list.contains(e))) { - tabOrder = list; - } - } - } - } catch (e) { - debugPrintStack(label: '$e'); - } - // init visibleOrderedTabs - var tempList = tabOrder.toList(); - tempList.removeWhere((e) => !tabs.contains(e)); - visibleOrderedTabs.value = tempList; - // init currentTab - currentTab.value = - int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ?? 0; - if (!tabs.contains(currentTab.value)) { - if (tabs.isNotEmpty) { - currentTab.value = tabs[0]; - } else { - currentTab.value = 0; - } - } - } - static final StatePeerTab instance = StatePeerTab._(); - - // check dynamic tabs - check() { - tabOrder2visibleOrderedTabs(); - if (visibleOrderedTabs.contains(groupTabIndex) && - int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) == - groupTabIndex) { - currentTab.value = groupTabIndex; - } - if (gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isNotEmpty) { - tabNames[groupTabIndex] = gFFI.userModel.groupName.value; - } else { - tabNames[groupTabIndex] = defaultGroupTabname; - } - } - - visibleOrderedTabs2TabOrder() { - var tmpTabOrder = visibleOrderedTabs.toList(); - var left = tabOrder.where((e) => !tmpTabOrder.contains(e)).toList(); - for (var t in left) { - _addTabInOrder(tmpTabOrder, t); - } - statePeerTab.tabOrder = tmpTabOrder; - bind.setLocalFlutterConfig(k: 'peer-tab-order', v: jsonEncode(tmpTabOrder)); - } - - tabOrder2visibleOrderedTabs() { - var visible = statePeerTab.visibleTabs(); - statePeerTab.visibleOrderedTabs.value = - statePeerTab.tabOrder.where((e) => visible.contains(e)).toList(); - } - - // return true if hide group card - bool filterGroupCard() { - if (gFFI.groupModel.users.isEmpty || - (gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isEmpty)) { - return true; - } else { - return false; - } - } - - // return index array of tabNames - List visibleTabs() { - var v = List.empty(growable: true); - for (int i = 0; i < tabNames.length; i++) { - if (!_isTabHidden(i) && !_isTabFilter(i)) { - v.add(i); - } - } - return v; - } - - bool _isTabHidden(int tabindex) { - return tabHiddenFlag & (1 << tabindex) != 0; - } - - bool _isTabFilter(int tabIndex) { - if (tabIndex == groupTabIndex) { - return filterGroupCard(); - } - return false; - } - - List _notHiddenTabs() { - var v = List.empty(growable: true); - for (int i = 0; i < tabNames.length; i++) { - if (!_isTabHidden(i)) { - v.add(i); - } - } - return v; - } - - // add tabIndex to list - _addTabInOrder(List list, int tabIndex) { - if (!tabOrder.contains(tabIndex) || list.contains(tabIndex)) { - return; - } - bool sameOrder = true; - int lastIndex = -1; - for (int i = 0; i < list.length; i++) { - var index = tabOrder.lastIndexOf(list[i]); - if (index > lastIndex) { - lastIndex = index; - continue; - } else { - sameOrder = false; - break; - } - } - if (sameOrder) { - var indexInTabOrder = tabOrder.indexOf(tabIndex); - var left = List.empty(growable: true); - for (int i = 0; i < indexInTabOrder; i++) { - left.add(tabOrder[i]); - } - int insertIndex = list.lastIndexWhere((e) => left.contains(e)); - if (insertIndex < 0) { - insertIndex = 0; - } else { - insertIndex += 1; - } - list.insert(insertIndex, tabIndex); - } else { - list.add(tabIndex); - } - } -} - -final statePeerTab = StatePeerTab.instance; - class PeerTabPage extends StatefulWidget { const PeerTabPage({Key? key}) : super(key: key); @override @@ -232,11 +66,10 @@ class _PeerTabPageState extends State ), () => {}), ]; + final _scrollDebounce = Debouncer(delay: Duration(milliseconds: 50)); @override void initState() { - adjustTab(); - final uiType = bind.getLocalFlutterConfig(k: 'peer-card-ui-type'); if (uiType != '') { peerCardUiType.value = int.parse(uiType) == PeerUiType.list.index @@ -248,7 +81,7 @@ class _PeerTabPageState extends State Future handleTabSelection(int tabIndex) async { if (tabIndex < entries.length) { - statePeerTab.currentTab.value = tabIndex; + gFFI.peerTabModel.setCurrentTab(tabIndex); entries[tabIndex].load(); } } @@ -270,6 +103,7 @@ class _PeerTabPageState extends State Expanded( child: visibleContextMenuListener( _createSwitchBar(context))), + buildScrollJumper(), const PeerSearchBar(), Offstage( offstage: !isDesktop, @@ -284,98 +118,115 @@ class _PeerTabPageState extends State } Widget _createSwitchBar(BuildContext context) { - return Obx(() { - var tabs = statePeerTab.visibleOrderedTabs; - int indexCounter = -1; - return ReorderableListView( - buildDefaultDragHandles: false, - onReorder: (oldIndex, newIndex) { - if (oldIndex < newIndex) { - newIndex -= 1; - } - var list = tabs.toList(); - final int item = list.removeAt(oldIndex); - list.insert(newIndex, item); - tabs.value = list; - statePeerTab.visibleOrderedTabs2TabOrder(); - }, - scrollDirection: Axis.horizontal, - physics: NeverScrollableScrollPhysics(), - scrollController: ScrollController(), - children: tabs.map((t) { - indexCounter++; - return ReorderableDragStartListener( + final model = Provider.of(context); + int indexCounter = -1; + return ReorderableListView( + buildDefaultDragHandles: false, + onReorder: (oldIndex, newIndex) { + model.onReorder(oldIndex, newIndex); + }, + scrollDirection: Axis.horizontal, + physics: NeverScrollableScrollPhysics(), + scrollController: model.sc, + children: model.visibleOrderedTabs.map((t) { + indexCounter++; + return ReorderableDragStartListener( + key: ValueKey(t), + index: indexCounter, + child: VisibilityDetector( key: ValueKey(t), - index: indexCounter, - child: InkWell( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - color: statePeerTab.currentTab.value == t - ? Theme.of(context).backgroundColor - : null, - borderRadius: BorderRadius.circular(isDesktop ? 2 : 6), - ), - child: Align( - alignment: Alignment.center, - child: Text( - translatedTabname(t), - textAlign: TextAlign.center, - style: TextStyle( - height: 1, - fontSize: 14, - color: statePeerTab.currentTab.value == t - ? MyTheme.tabbar(context).selectedTextColor - : MyTheme.tabbar(context).unSelectedTextColor - ?..withOpacity(0.5)), - ), - )), - onTap: () async { - await handleTabSelection(t); - await bind.setLocalFlutterConfig( - k: 'peer-tab-index', v: t.toString()); + onVisibilityChanged: (info) { + final id = (info.key as ValueKey).value; + model.setTabFullyVisible(id, info.visibleFraction > 0.99); + }, + child: Listener( + // handle mouse wheel + onPointerSignal: (e) { + if (e is PointerScrollEvent) { + if (!model.sc.canScroll) return; + _scrollDebounce.call(() { + model.sc.animateTo(model.sc.offset + e.scrollDelta.dy, + duration: Duration(milliseconds: 200), + curve: Curves.ease); + }); + } }, + child: InkWell( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: model.currentTab == t + ? Theme.of(context).backgroundColor + : null, + borderRadius: BorderRadius.circular(isDesktop ? 2 : 6), + ), + child: Align( + alignment: Alignment.center, + child: Text( + model.translatedTabname(t), + textAlign: TextAlign.center, + style: TextStyle( + height: 1, + fontSize: 14, + color: model.currentTab == t + ? MyTheme.tabbar(context).selectedTextColor + : MyTheme.tabbar(context).unSelectedTextColor + ?..withOpacity(0.5)), + ), + )), + onTap: () async { + await handleTabSelection(t); + await bind.setLocalFlutterConfig( + k: 'peer-tab-index', v: t.toString()); + }, + ), ), - ); - }).toList()); - }); + ), + ); + }).toList()); } - translatedTabname(int index) { - if (index < statePeerTab.tabNames.length) { - final name = statePeerTab.tabNames[index]; - if (index == groupTabIndex) { - if (name == defaultGroupTabname) { - return translate(name); - } else { - return name; - } - } else { - return translate(name); - } - } - assert(false); - return index.toString(); + Widget buildScrollJumper() { + final model = Provider.of(context); + return Offstage( + offstage: !model.showScrollBtn, + child: Row( + children: [ + GestureDetector( + child: Icon(Icons.arrow_left, + size: 22, + color: model.leftFullyVisible + ? Theme.of(context).disabledColor + : null), + onTap: model.sc.backward), + GestureDetector( + child: Icon(Icons.arrow_right, + size: 22, + color: model.rightFullyVisible + ? Theme.of(context).disabledColor + : null), + onTap: model.sc.forward) + ], + )); } Widget _createPeersView() { - final verticalMargin = isDesktop ? 12.0 : 6.0; - return Expanded( - child: Obx(() { - var tabs = statePeerTab.visibleOrderedTabs; - if (tabs.isEmpty) { - return visibleContextMenuListener(Center( - child: Text(translate('Right click to select tabs')), - )); + final model = Provider.of(context); + Widget child; + if (model.visibleOrderedTabs.isEmpty) { + child = visibleContextMenuListener(Center( + child: Text(translate('Right click to select tabs')), + )); + } else { + if (model.visibleOrderedTabs.contains(model.currentTab)) { + child = entries[model.currentTab].widget; } else { - if (tabs.contains(statePeerTab.currentTab.value)) { - return entries[statePeerTab.currentTab.value].widget; - } else { - statePeerTab.currentTab.value = tabs[0]; - return entries[statePeerTab.currentTab.value].widget; - } + model.setCurrentTab(model.visibleOrderedTabs[0]); + child = entries[0].widget; } - }).marginSymmetric(vertical: verticalMargin)); + } + return Expanded( + child: child.marginSymmetric(vertical: isDesktop ? 12.0 : 6.0)); } Widget _createPeerViewTypeSwitch(BuildContext context) { @@ -408,13 +259,6 @@ class _PeerTabPageState extends State ); } - adjustTab() { - var tabs = statePeerTab.visibleOrderedTabs; - if (tabs.isNotEmpty && !tabs.contains(statePeerTab.currentTab.value)) { - statePeerTab.currentTab.value = tabs[0]; - } - } - Widget visibleContextMenuListener(Widget child) { return Listener( onPointerDown: (e) { @@ -434,55 +278,36 @@ class _PeerTabPageState extends State } Widget visibleContextMenu(CancelFunc cancelFunc) { - return Obx(() { - final List menu = List.empty(growable: true); - final List menuIndex = List.empty(growable: true); - for (int i = 0; i < statePeerTab.tabNames.length; i++) { - if (i == groupTabIndex && statePeerTab.filterGroupCard()) { - continue; - } - int bitMask = 1 << i; - menuIndex.add(i); - menu.add(MenuEntrySwitch( - switchType: SwitchType.scheckbox, - text: translatedTabname(i), - getter: () async { - return statePeerTab.tabHiddenFlag & bitMask == 0; - }, - setter: (show) async { - if (show) { - statePeerTab.tabHiddenFlag.value &= ~bitMask; - } else { - statePeerTab.tabHiddenFlag.value |= bitMask; - } - await bind.setLocalFlutterConfig( - k: 'hidden-peer-card', - v: statePeerTab.tabHiddenFlag.value.toRadixString(2)); - statePeerTab.tabOrder2visibleOrderedTabs(); - cancelFunc(); - adjustTab(); - })); - } - // show in tabOrder - List menu2 = List.empty(growable: true); - statePeerTab.tabOrder.map((e) { - final index = menuIndex.indexOf(e); - if (index >= 0) { - menu2.add(menu[index]); - } - }).toList(); - return mod_menu.PopupMenu( - items: menu2 - .map((entry) => entry.build( - context, - const MenuConfig( - commonColor: MyTheme.accent, - height: 20.0, - dividerHeight: 12.0, - ))) - .expand((i) => i) - .toList()); - }); + final model = Provider.of(context); + final List menu = List.empty(growable: true); + final List menuIndex = List.empty(growable: true); + var list = model.orderedNotFilteredTabs(); + for (int i = 0; i < list.length; i++) { + int tabIndex = list[i]; + int bitMask = 1 << tabIndex; + menuIndex.add(tabIndex); + menu.add(MenuEntrySwitch( + switchType: SwitchType.scheckbox, + text: model.translatedTabname(tabIndex), + getter: () async { + return model.tabHiddenFlag & bitMask == 0; + }, + setter: (show) async { + model.onHideShow(tabIndex, show); + cancelFunc(); + })); + } + return mod_menu.PopupMenu( + items: menu + .map((entry) => entry.build( + context, + const MenuConfig( + commonColor: MyTheme.accent, + height: 20.0, + dividerHeight: 12.0, + ))) + .expand((i) => i) + .toList()); } } diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 86cc9d89b..a2ae959c0 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -353,6 +353,7 @@ class _AppState extends State { ChangeNotifierProvider.value(value: gFFI.imageModel), ChangeNotifierProvider.value(value: gFFI.cursorModel), ChangeNotifierProvider.value(value: gFFI.canvasModel), + ChangeNotifierProvider.value(value: gFFI.peerTabModel), ], child: GetMaterialApp( navigatorKey: globalKey, diff --git a/flutter/lib/mobile/widgets/dialog.dart b/flutter/lib/mobile/widgets/dialog.dart index 2fbe40091..7e9a9879c 100644 --- a/flutter/lib/mobile/widgets/dialog.dart +++ b/flutter/lib/mobile/widgets/dialog.dart @@ -643,9 +643,9 @@ class _PasswordWidgetState extends State { // Here is key idea suffixIcon: IconButton( icon: Icon( - // Based on passwordVisible state choose the icon - _passwordVisible ? Icons.visibility : Icons.visibility_off, - ), + // Based on passwordVisible state choose the icon + _passwordVisible ? Icons.visibility : Icons.visibility_off, + color: MyTheme.lightTheme.primaryColor), onPressed: () { // Update the state i.e. toggle the state of passwordVisible variable setState(() { diff --git a/flutter/lib/models/group_model.dart b/flutter/lib/models/group_model.dart index 4d9fab0e4..5e2b85f90 100644 --- a/flutter/lib/models/group_model.dart +++ b/flutter/lib/models/group_model.dart @@ -35,7 +35,7 @@ class GroupModel { await reset(); if (gFFI.userModel.userName.isEmpty || (gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isEmpty)) { - statePeerTab.check(); + gFFI.peerTabModel.check_dynamic_tabs(); return; } userLoading.value = true; @@ -82,7 +82,7 @@ class GroupModel { userLoadError.value = err.toString(); } finally { userLoading.value = false; - statePeerTab.check(); + gFFI.peerTabModel.check_dynamic_tabs(); } } diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index aae4c6a07..daf7bfe34 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -13,6 +13,7 @@ import 'package:flutter_hbb/models/ab_model.dart'; import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/file_model.dart'; import 'package:flutter_hbb/models/group_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'; import 'package:flutter_hbb/models/state_model.dart'; @@ -1292,8 +1293,9 @@ class FFI { late final AbModel abModel; // global late final GroupModel groupModel; // global late final UserModel userModel; // global + late final PeerTabModel peerTabModel; // global late final QualityMonitorModel qualityMonitorModel; // session - late final RecordingModel recordingModel; // recording + late final RecordingModel recordingModel; // session late final InputModel inputModel; // session FFI() { @@ -1305,6 +1307,7 @@ class FFI { chatModel = ChatModel(WeakReference(this)); fileModel = FileModel(WeakReference(this)); userModel = UserModel(WeakReference(this)); + peerTabModel = PeerTabModel(WeakReference(this)); abModel = AbModel(WeakReference(this)); groupModel = GroupModel(WeakReference(this)); qualityMonitorModel = QualityMonitorModel(WeakReference(this)); diff --git a/flutter/lib/models/peer_tab_model.dart b/flutter/lib/models/peer_tab_model.dart new file mode 100644 index 000000000..7c6211682 --- /dev/null +++ b/flutter/lib/models/peer_tab_model.dart @@ -0,0 +1,275 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hbb/models/platform_model.dart'; +import 'package:get/get.dart'; +import 'package:scroll_pos/scroll_pos.dart'; + +import '../common.dart'; +import 'model.dart'; + +const int groupTabIndex = 4; +const String defaultGroupTabname = 'Group'; + +class PeerTabModel with ChangeNotifier { + WeakReference parent; + int get currentTab => _currentTab; + int _currentTab = 0; // index in tabNames + List get visibleOrderedTabs => _visibleOrderedTabs; + List _visibleOrderedTabs = List.empty(growable: true); + List get tabOrder => _tabOrder; + List _tabOrder = List.from([0, 1, 2, 3, 4]); // constant length + int get tabHiddenFlag => _tabHiddenFlag; + int _tabHiddenFlag = 0; + bool get showScrollBtn => _showScrollBtn; + bool _showScrollBtn = false; + final List _fullyVisible = List.filled(5, false); + bool get leftFullyVisible => _leftFullyVisible; + bool _leftFullyVisible = false; + bool get rightFullyVisible => _rightFullyVisible; + bool _rightFullyVisible = false; + ScrollPosController sc = ScrollPosController(); + List tabNames = [ + 'Recent Sessions', + 'Favorites', + 'Discovered', + 'Address Book', + defaultGroupTabname, + ]; + + PeerTabModel(this.parent) { + // init tabHiddenFlag + _tabHiddenFlag = int.tryParse( + bind.getLocalFlutterConfig(k: 'hidden-peer-card'), + radix: 2) ?? + 0; + var tabs = _notHiddenTabs(); + // remove dynamic tabs + tabs.remove(groupTabIndex); + // init tabOrder + try { + final conf = bind.getLocalFlutterConfig(k: 'peer-tab-order'); + if (conf.isNotEmpty) { + final json = jsonDecode(conf); + if (json is List) { + final List list = + json.map((e) => int.tryParse(e.toString()) ?? -1).toList(); + if (list.length == _tabOrder.length && + _tabOrder.every((e) => list.contains(e))) { + _tabOrder = list; + } + } + } + } catch (e) { + debugPrintStack(label: '$e'); + } + // init visibleOrderedTabs + var tempList = _tabOrder.toList(); + tempList.removeWhere((e) => !tabs.contains(e)); + _visibleOrderedTabs = tempList; + // init currentTab + _currentTab = + int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) ?? 0; + if (!tabs.contains(_currentTab)) { + if (tabs.isNotEmpty) { + _currentTab = tabs[0]; + } else { + _currentTab = 0; + } + } + sc.itemCount = _visibleOrderedTabs.length; + } + + check_dynamic_tabs() { + var visible = visibleTabs(); + _visibleOrderedTabs = _tabOrder.where((e) => visible.contains(e)).toList(); + if (_visibleOrderedTabs.contains(groupTabIndex) && + int.tryParse(bind.getLocalFlutterConfig(k: 'peer-tab-index')) == + groupTabIndex) { + _currentTab = groupTabIndex; + } + if (gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isNotEmpty) { + tabNames[groupTabIndex] = gFFI.userModel.groupName.value; + } else { + tabNames[groupTabIndex] = defaultGroupTabname; + } + sc.itemCount = _visibleOrderedTabs.length; + notifyListeners(); + } + + setCurrentTab(int index) { + if (_currentTab != index) { + _currentTab = index; + notifyListeners(); + } + } + + setTabFullyVisible(int index, bool visible) { + if (index >= 0 && index < _fullyVisible.length) { + if (visible != _fullyVisible[index]) { + _fullyVisible[index] = visible; + bool changed = false; + bool show = _visibleOrderedTabs.any((e) => !_fullyVisible[e]); + if (show != _showScrollBtn) { + _showScrollBtn = show; + changed = true; + } + if (_visibleOrderedTabs.isNotEmpty && _visibleOrderedTabs[0] == index) { + if (_leftFullyVisible != visible) { + _leftFullyVisible = visible; + changed = true; + } + } + if (_visibleOrderedTabs.isNotEmpty && + _visibleOrderedTabs.last == index) { + if (_rightFullyVisible != visible) { + _rightFullyVisible = visible; + changed = true; + } + } + if (changed) { + notifyListeners(); + } + } + } + } + + onReorder(oldIndex, newIndex) { + if (oldIndex < newIndex) { + newIndex -= 1; + } + var list = _visibleOrderedTabs.toList(); + final int item = list.removeAt(oldIndex); + list.insert(newIndex, item); + _visibleOrderedTabs = list; + + var tmpTabOrder = _visibleOrderedTabs.toList(); + var left = _tabOrder.where((e) => !tmpTabOrder.contains(e)).toList(); + for (var t in left) { + _addTabInOrder(tmpTabOrder, t); + } + _tabOrder = tmpTabOrder; + bind.setLocalFlutterConfig(k: 'peer-tab-order', v: jsonEncode(tmpTabOrder)); + notifyListeners(); + } + + onHideShow(int index, bool show) async { + int bitMask = 1 << index; + if (show) { + _tabHiddenFlag &= ~bitMask; + } else { + _tabHiddenFlag |= bitMask; + } + await bind.setLocalFlutterConfig( + k: 'hidden-peer-card', v: _tabHiddenFlag.toRadixString(2)); + var visible = visibleTabs(); + _visibleOrderedTabs = _tabOrder.where((e) => visible.contains(e)).toList(); + if (_visibleOrderedTabs.isNotEmpty && + !_visibleOrderedTabs.contains(_currentTab)) { + _currentTab = _visibleOrderedTabs[0]; + } + notifyListeners(); + } + + List orderedNotFilteredTabs() { + var list = tabOrder.toList(); + if (_filterGroupCard()) { + list.remove(groupTabIndex); + } + return list; + } + + // return index array of tabNames + List visibleTabs() { + var v = List.empty(growable: true); + for (int i = 0; i < tabNames.length; i++) { + if (!_isTabHidden(i) && !_isTabFilter(i)) { + v.add(i); + } + } + return v; + } + + String translatedTabname(int index) { + if (index >= 0 && index < tabNames.length) { + final name = tabNames[index]; + if (index == groupTabIndex) { + if (name == defaultGroupTabname) { + return translate(name); + } else { + return name; + } + } else { + return translate(name); + } + } + assert(false); + return index.toString(); + } + + bool _isTabHidden(int tabindex) { + return _tabHiddenFlag & (1 << tabindex) != 0; + } + + bool _isTabFilter(int tabIndex) { + if (tabIndex == groupTabIndex) { + return _filterGroupCard(); + } + return false; + } + + // return true if hide group card + bool _filterGroupCard() { + if (gFFI.groupModel.users.isEmpty || + (gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isEmpty)) { + return true; + } else { + return false; + } + } + + List _notHiddenTabs() { + var v = List.empty(growable: true); + for (int i = 0; i < tabNames.length; i++) { + if (!_isTabHidden(i)) { + v.add(i); + } + } + return v; + } + + // add tabIndex to list + _addTabInOrder(List list, int tabIndex) { + if (!_tabOrder.contains(tabIndex) || list.contains(tabIndex)) { + return; + } + bool sameOrder = true; + int lastIndex = -1; + for (int i = 0; i < list.length; i++) { + var index = _tabOrder.lastIndexOf(list[i]); + if (index > lastIndex) { + lastIndex = index; + continue; + } else { + sameOrder = false; + break; + } + } + if (sameOrder) { + var indexInTabOrder = _tabOrder.indexOf(tabIndex); + var left = List.empty(growable: true); + for (int i = 0; i < indexInTabOrder; i++) { + left.add(_tabOrder[i]); + } + int insertIndex = list.lastIndexWhere((e) => left.contains(e)); + if (insertIndex < 0) { + insertIndex = 0; + } else { + insertIndex += 1; + } + list.insert(insertIndex, tabIndex); + } else { + list.add(tabIndex); + } + } +} diff --git a/flutter/lib/models/user_model.dart b/flutter/lib/models/user_model.dart index 6694d8c5c..7f40b3333 100644 --- a/flutter/lib/models/user_model.dart +++ b/flutter/lib/models/user_model.dart @@ -62,7 +62,7 @@ class UserModel { await gFFI.groupModel.reset(); userName.value = ''; groupName.value = ''; - statePeerTab.check(); + gFFI.peerTabModel.check_dynamic_tabs(); } Future _parseAndUpdateUser(UserPayload user) async {