refactor peer tab with model, make it scrollable

Signed-off-by: 21pages <pages21@163.com>
This commit is contained in:
21pages 2023-02-03 15:07:45 +08:00
parent 133fba573b
commit 40d0ea016b
7 changed files with 423 additions and 319 deletions

View file

@ -1,7 +1,7 @@
import 'dart:convert';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:bot_toast/bot_toast.dart'; import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/address_book.dart'; import 'package:flutter_hbb/common/widgets/address_book.dart';
import 'package:flutter_hbb/common/widgets/my_group.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/tabbar_widget.dart';
import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart' import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart'
as mod_menu; as mod_menu;
import 'package:flutter_hbb/models/peer_tab_model.dart';
import 'package:get/get.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 '../../common.dart';
import '../../models/platform_model.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<int> visibleOrderedTabs = RxList.empty(growable: true);
List<int> tabOrder = List.from([0, 1, 2, 3, 4]); // constant length
final RxInt tabHiddenFlag = 0.obs;
final RxList<String> 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<int> 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<int> visibleTabs() {
var v = List<int>.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<int> _notHiddenTabs() {
var v = List<int>.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<int> 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 { class PeerTabPage extends StatefulWidget {
const PeerTabPage({Key? key}) : super(key: key); const PeerTabPage({Key? key}) : super(key: key);
@override @override
@ -232,11 +66,10 @@ class _PeerTabPageState extends State<PeerTabPage>
), ),
() => {}), () => {}),
]; ];
final _scrollDebounce = Debouncer(delay: Duration(milliseconds: 50));
@override @override
void initState() { void initState() {
adjustTab();
final uiType = bind.getLocalFlutterConfig(k: 'peer-card-ui-type'); final uiType = bind.getLocalFlutterConfig(k: 'peer-card-ui-type');
if (uiType != '') { if (uiType != '') {
peerCardUiType.value = int.parse(uiType) == PeerUiType.list.index peerCardUiType.value = int.parse(uiType) == PeerUiType.list.index
@ -248,7 +81,7 @@ class _PeerTabPageState extends State<PeerTabPage>
Future<void> handleTabSelection(int tabIndex) async { Future<void> handleTabSelection(int tabIndex) async {
if (tabIndex < entries.length) { if (tabIndex < entries.length) {
statePeerTab.currentTab.value = tabIndex; gFFI.peerTabModel.setCurrentTab(tabIndex);
entries[tabIndex].load(); entries[tabIndex].load();
} }
} }
@ -270,6 +103,7 @@ class _PeerTabPageState extends State<PeerTabPage>
Expanded( Expanded(
child: visibleContextMenuListener( child: visibleContextMenuListener(
_createSwitchBar(context))), _createSwitchBar(context))),
buildScrollJumper(),
const PeerSearchBar(), const PeerSearchBar(),
Offstage( Offstage(
offstage: !isDesktop, offstage: !isDesktop,
@ -284,34 +118,44 @@ class _PeerTabPageState extends State<PeerTabPage>
} }
Widget _createSwitchBar(BuildContext context) { Widget _createSwitchBar(BuildContext context) {
return Obx(() { final model = Provider.of<PeerTabModel>(context);
var tabs = statePeerTab.visibleOrderedTabs;
int indexCounter = -1; int indexCounter = -1;
return ReorderableListView( return ReorderableListView(
buildDefaultDragHandles: false, buildDefaultDragHandles: false,
onReorder: (oldIndex, newIndex) { onReorder: (oldIndex, newIndex) {
if (oldIndex < newIndex) { model.onReorder(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, scrollDirection: Axis.horizontal,
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
scrollController: ScrollController(), scrollController: model.sc,
children: tabs.map((t) { children: model.visibleOrderedTabs.map((t) {
indexCounter++; indexCounter++;
return ReorderableDragStartListener( return ReorderableDragStartListener(
key: ValueKey(t), key: ValueKey(t),
index: indexCounter, index: indexCounter,
child: VisibilityDetector(
key: ValueKey(t),
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: InkWell(
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: statePeerTab.currentTab.value == t color: model.currentTab == t
? Theme.of(context).backgroundColor ? Theme.of(context).backgroundColor
: null, : null,
borderRadius: BorderRadius.circular(isDesktop ? 2 : 6), borderRadius: BorderRadius.circular(isDesktop ? 2 : 6),
@ -319,12 +163,12 @@ class _PeerTabPageState extends State<PeerTabPage>
child: Align( child: Align(
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
translatedTabname(t), model.translatedTabname(t),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
height: 1, height: 1,
fontSize: 14, fontSize: 14,
color: statePeerTab.currentTab.value == t color: model.currentTab == t
? MyTheme.tabbar(context).selectedTextColor ? MyTheme.tabbar(context).selectedTextColor
: MyTheme.tabbar(context).unSelectedTextColor : MyTheme.tabbar(context).unSelectedTextColor
?..withOpacity(0.5)), ?..withOpacity(0.5)),
@ -336,46 +180,53 @@ class _PeerTabPageState extends State<PeerTabPage>
k: 'peer-tab-index', v: t.toString()); k: 'peer-tab-index', v: t.toString());
}, },
), ),
),
),
); );
}).toList()); }).toList());
});
} }
translatedTabname(int index) { Widget buildScrollJumper() {
if (index < statePeerTab.tabNames.length) { final model = Provider.of<PeerTabModel>(context);
final name = statePeerTab.tabNames[index]; return Offstage(
if (index == groupTabIndex) { offstage: !model.showScrollBtn,
if (name == defaultGroupTabname) { child: Row(
return translate(name); children: [
} else { GestureDetector(
return name; child: Icon(Icons.arrow_left,
} size: 22,
} else { color: model.leftFullyVisible
return translate(name); ? Theme.of(context).disabledColor
} : null),
} onTap: model.sc.backward),
assert(false); GestureDetector(
return index.toString(); child: Icon(Icons.arrow_right,
size: 22,
color: model.rightFullyVisible
? Theme.of(context).disabledColor
: null),
onTap: model.sc.forward)
],
));
} }
Widget _createPeersView() { Widget _createPeersView() {
final verticalMargin = isDesktop ? 12.0 : 6.0; final model = Provider.of<PeerTabModel>(context);
return Expanded( Widget child;
child: Obx(() { if (model.visibleOrderedTabs.isEmpty) {
var tabs = statePeerTab.visibleOrderedTabs; child = visibleContextMenuListener(Center(
if (tabs.isEmpty) {
return visibleContextMenuListener(Center(
child: Text(translate('Right click to select tabs')), child: Text(translate('Right click to select tabs')),
)); ));
} else { } else {
if (tabs.contains(statePeerTab.currentTab.value)) { if (model.visibleOrderedTabs.contains(model.currentTab)) {
return entries[statePeerTab.currentTab.value].widget; child = entries[model.currentTab].widget;
} else { } else {
statePeerTab.currentTab.value = tabs[0]; model.setCurrentTab(model.visibleOrderedTabs[0]);
return entries[statePeerTab.currentTab.value].widget; child = entries[0].widget;
} }
} }
}).marginSymmetric(vertical: verticalMargin)); return Expanded(
child: child.marginSymmetric(vertical: isDesktop ? 12.0 : 6.0));
} }
Widget _createPeerViewTypeSwitch(BuildContext context) { Widget _createPeerViewTypeSwitch(BuildContext context) {
@ -408,13 +259,6 @@ class _PeerTabPageState extends State<PeerTabPage>
); );
} }
adjustTab() {
var tabs = statePeerTab.visibleOrderedTabs;
if (tabs.isNotEmpty && !tabs.contains(statePeerTab.currentTab.value)) {
statePeerTab.currentTab.value = tabs[0];
}
}
Widget visibleContextMenuListener(Widget child) { Widget visibleContextMenuListener(Widget child) {
return Listener( return Listener(
onPointerDown: (e) { onPointerDown: (e) {
@ -434,45 +278,27 @@ class _PeerTabPageState extends State<PeerTabPage>
} }
Widget visibleContextMenu(CancelFunc cancelFunc) { Widget visibleContextMenu(CancelFunc cancelFunc) {
return Obx(() { final model = Provider.of<PeerTabModel>(context);
final List<MenuEntryBase> menu = List.empty(growable: true); final List<MenuEntryBase> menu = List.empty(growable: true);
final List<int> menuIndex = List.empty(growable: true); final List<int> menuIndex = List.empty(growable: true);
for (int i = 0; i < statePeerTab.tabNames.length; i++) { var list = model.orderedNotFilteredTabs();
if (i == groupTabIndex && statePeerTab.filterGroupCard()) { for (int i = 0; i < list.length; i++) {
continue; int tabIndex = list[i];
} int bitMask = 1 << tabIndex;
int bitMask = 1 << i; menuIndex.add(tabIndex);
menuIndex.add(i);
menu.add(MenuEntrySwitch( menu.add(MenuEntrySwitch(
switchType: SwitchType.scheckbox, switchType: SwitchType.scheckbox,
text: translatedTabname(i), text: model.translatedTabname(tabIndex),
getter: () async { getter: () async {
return statePeerTab.tabHiddenFlag & bitMask == 0; return model.tabHiddenFlag & bitMask == 0;
}, },
setter: (show) async { setter: (show) async {
if (show) { model.onHideShow(tabIndex, 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(); cancelFunc();
adjustTab();
})); }));
} }
// show in tabOrder
List<MenuEntryBase> 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( return mod_menu.PopupMenu(
items: menu2 items: menu
.map((entry) => entry.build( .map((entry) => entry.build(
context, context,
const MenuConfig( const MenuConfig(
@ -482,7 +308,6 @@ class _PeerTabPageState extends State<PeerTabPage>
))) )))
.expand((i) => i) .expand((i) => i)
.toList()); .toList());
});
} }
} }

View file

@ -353,6 +353,7 @@ class _AppState extends State<App> {
ChangeNotifierProvider.value(value: gFFI.imageModel), ChangeNotifierProvider.value(value: gFFI.imageModel),
ChangeNotifierProvider.value(value: gFFI.cursorModel), ChangeNotifierProvider.value(value: gFFI.cursorModel),
ChangeNotifierProvider.value(value: gFFI.canvasModel), ChangeNotifierProvider.value(value: gFFI.canvasModel),
ChangeNotifierProvider.value(value: gFFI.peerTabModel),
], ],
child: GetMaterialApp( child: GetMaterialApp(
navigatorKey: globalKey, navigatorKey: globalKey,

View file

@ -645,7 +645,7 @@ class _PasswordWidgetState extends State<PasswordWidget> {
icon: Icon( icon: Icon(
// Based on passwordVisible state choose the icon // Based on passwordVisible state choose the icon
_passwordVisible ? Icons.visibility : Icons.visibility_off, _passwordVisible ? Icons.visibility : Icons.visibility_off,
), color: MyTheme.lightTheme.primaryColor),
onPressed: () { onPressed: () {
// Update the state i.e. toggle the state of passwordVisible variable // Update the state i.e. toggle the state of passwordVisible variable
setState(() { setState(() {

View file

@ -35,7 +35,7 @@ class GroupModel {
await reset(); await reset();
if (gFFI.userModel.userName.isEmpty || if (gFFI.userModel.userName.isEmpty ||
(gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isEmpty)) { (gFFI.userModel.isAdmin.isFalse && gFFI.userModel.groupName.isEmpty)) {
statePeerTab.check(); gFFI.peerTabModel.check_dynamic_tabs();
return; return;
} }
userLoading.value = true; userLoading.value = true;
@ -82,7 +82,7 @@ class GroupModel {
userLoadError.value = err.toString(); userLoadError.value = err.toString();
} finally { } finally {
userLoading.value = false; userLoading.value = false;
statePeerTab.check(); gFFI.peerTabModel.check_dynamic_tabs();
} }
} }

View file

@ -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/chat_model.dart';
import 'package:flutter_hbb/models/file_model.dart'; import 'package:flutter_hbb/models/file_model.dart';
import 'package:flutter_hbb/models/group_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/server_model.dart';
import 'package:flutter_hbb/models/user_model.dart'; import 'package:flutter_hbb/models/user_model.dart';
import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/models/state_model.dart';
@ -1292,8 +1293,9 @@ class FFI {
late final AbModel abModel; // global late final AbModel abModel; // global
late final GroupModel groupModel; // global late final GroupModel groupModel; // global
late final UserModel userModel; // global late final UserModel userModel; // global
late final PeerTabModel peerTabModel; // global
late final QualityMonitorModel qualityMonitorModel; // session late final QualityMonitorModel qualityMonitorModel; // session
late final RecordingModel recordingModel; // recording late final RecordingModel recordingModel; // session
late final InputModel inputModel; // session late final InputModel inputModel; // session
FFI() { FFI() {
@ -1305,6 +1307,7 @@ class FFI {
chatModel = ChatModel(WeakReference(this)); chatModel = ChatModel(WeakReference(this));
fileModel = FileModel(WeakReference(this)); fileModel = FileModel(WeakReference(this));
userModel = UserModel(WeakReference(this)); userModel = UserModel(WeakReference(this));
peerTabModel = PeerTabModel(WeakReference(this));
abModel = AbModel(WeakReference(this)); abModel = AbModel(WeakReference(this));
groupModel = GroupModel(WeakReference(this)); groupModel = GroupModel(WeakReference(this));
qualityMonitorModel = QualityMonitorModel(WeakReference(this)); qualityMonitorModel = QualityMonitorModel(WeakReference(this));

View file

@ -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<FFI> parent;
int get currentTab => _currentTab;
int _currentTab = 0; // index in tabNames
List<int> get visibleOrderedTabs => _visibleOrderedTabs;
List<int> _visibleOrderedTabs = List.empty(growable: true);
List<int> get tabOrder => _tabOrder;
List<int> _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<bool> _fullyVisible = List.filled(5, false);
bool get leftFullyVisible => _leftFullyVisible;
bool _leftFullyVisible = false;
bool get rightFullyVisible => _rightFullyVisible;
bool _rightFullyVisible = false;
ScrollPosController sc = ScrollPosController();
List<String> 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<int> 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<int> orderedNotFilteredTabs() {
var list = tabOrder.toList();
if (_filterGroupCard()) {
list.remove(groupTabIndex);
}
return list;
}
// return index array of tabNames
List<int> visibleTabs() {
var v = List<int>.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<int> _notHiddenTabs() {
var v = List<int>.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<int> 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);
}
}
}

View file

@ -62,7 +62,7 @@ class UserModel {
await gFFI.groupModel.reset(); await gFFI.groupModel.reset();
userName.value = ''; userName.value = '';
groupName.value = ''; groupName.value = '';
statePeerTab.check(); gFFI.peerTabModel.check_dynamic_tabs();
} }
Future<void> _parseAndUpdateUser(UserPayload user) async { Future<void> _parseAndUpdateUser(UserPayload user) async {