reordered peer tab (#7604)
* reordered peer tab Signed-off-by: 21pages <pages21@163.com> * opt peer tab visible menu, avoid checkbox value splash Signed-off-by: 21pages <pages21@163.com> --------- Signed-off-by: 21pages <pages21@163.com>
This commit is contained in:
parent
5a0333ddaf
commit
0c294eefae
@ -137,11 +137,13 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
|
||||
Widget _createSwitchBar(BuildContext context) {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
|
||||
return ListView(
|
||||
var counter = -1;
|
||||
return ReorderableListView(
|
||||
buildDefaultDragHandles: false,
|
||||
onReorder: model.reorder,
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
children: model.visibleIndexs.map((t) {
|
||||
children: model.visibleEnabledOrderedIndexs.map((t) {
|
||||
final selected = model.currentTab == t;
|
||||
final color = selected
|
||||
? MyTheme.tabbar(context).selectedTextColor
|
||||
@ -155,43 +157,47 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
border: Border(
|
||||
bottom: BorderSide(width: 2, color: color!),
|
||||
));
|
||||
return Obx(() => Tooltip(
|
||||
preferBelow: false,
|
||||
message: model.tabTooltip(t),
|
||||
onTriggered: isMobile ? mobileShowTabVisibilityMenu : null,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
decoration: (hover.value
|
||||
? (selected ? decoBorder : deco)
|
||||
: (selected ? decoBorder : null)),
|
||||
child: Icon(model.tabIcon(t), color: color)
|
||||
.paddingSymmetric(horizontal: 4),
|
||||
).paddingSymmetric(horizontal: 4),
|
||||
onTap: () async {
|
||||
await handleTabSelection(t);
|
||||
await bind.setLocalFlutterOption(
|
||||
k: 'peer-tab-index', v: t.toString());
|
||||
},
|
||||
onHover: (value) => hover.value = value,
|
||||
),
|
||||
));
|
||||
counter += 1;
|
||||
return ReorderableDragStartListener(
|
||||
key: ValueKey(t),
|
||||
index: counter,
|
||||
child: Obx(() => Tooltip(
|
||||
preferBelow: false,
|
||||
message: model.tabTooltip(t),
|
||||
onTriggered: isMobile ? mobileShowTabVisibilityMenu : null,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
decoration: (hover.value
|
||||
? (selected ? decoBorder : deco)
|
||||
: (selected ? decoBorder : null)),
|
||||
child: Icon(model.tabIcon(t), color: color)
|
||||
.paddingSymmetric(horizontal: 4),
|
||||
).paddingSymmetric(horizontal: 4),
|
||||
onTap: () async {
|
||||
await handleTabSelection(t);
|
||||
await bind.setLocalFlutterOption(
|
||||
k: PeerTabModel.kPeerTabIndex, v: t.toString());
|
||||
},
|
||||
onHover: (value) => hover.value = value,
|
||||
),
|
||||
)));
|
||||
}).toList());
|
||||
}
|
||||
|
||||
Widget _createPeersView() {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
Widget child;
|
||||
if (model.visibleIndexs.isEmpty) {
|
||||
if (model.visibleEnabledOrderedIndexs.isEmpty) {
|
||||
child = visibleContextMenuListener(Row(
|
||||
children: [Expanded(child: InkWell())],
|
||||
));
|
||||
} else {
|
||||
if (model.visibleIndexs.contains(model.currentTab)) {
|
||||
if (model.visibleEnabledOrderedIndexs.contains(model.currentTab)) {
|
||||
child = entries[model.currentTab].widget;
|
||||
} else {
|
||||
debugPrint("should not happen! currentTab not in visibleIndexs");
|
||||
Future.delayed(Duration.zero, () {
|
||||
model.setCurrentTab(model.indexs[0]);
|
||||
model.setCurrentTab(model.visibleEnabledOrderedIndexs[0]);
|
||||
});
|
||||
child = entries[0].widget;
|
||||
}
|
||||
@ -255,16 +261,17 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
void mobileShowTabVisibilityMenu() {
|
||||
final model = gFFI.peerTabModel;
|
||||
final items = List<PopupMenuItem>.empty(growable: true);
|
||||
for (int i = 0; i < model.tabNames.length; i++) {
|
||||
for (int i = 0; i < PeerTabModel.maxTabCount; i++) {
|
||||
if (!model.isEnabled[i]) continue;
|
||||
items.add(PopupMenuItem(
|
||||
height: kMinInteractiveDimension * 0.8,
|
||||
onTap: () => model.setTabVisible(i, !model.isVisible[i]),
|
||||
onTap: () => model.setTabVisible(i, !model.isVisibleEnabled[i]),
|
||||
child: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: model.isVisible[i],
|
||||
value: model.isVisibleEnabled[i],
|
||||
onChanged: (_) {
|
||||
model.setTabVisible(i, !model.isVisible[i]);
|
||||
model.setTabVisible(i, !model.isVisibleEnabled[i]);
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
@ -314,16 +321,17 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
|
||||
Widget visibleContextMenu(CancelFunc cancelFunc) {
|
||||
final model = Provider.of<PeerTabModel>(context);
|
||||
final menu = List<MenuEntrySwitch>.empty(growable: true);
|
||||
for (int i = 0; i < model.tabNames.length; i++) {
|
||||
menu.add(MenuEntrySwitch(
|
||||
final menu = List<MenuEntrySwitchSync>.empty(growable: true);
|
||||
for (int i = 0; i < model.orders.length; i++) {
|
||||
int tabIndex = model.orders[i];
|
||||
if (tabIndex < 0 || tabIndex >= PeerTabModel.maxTabCount) continue;
|
||||
if (!model.isEnabled[tabIndex]) continue;
|
||||
menu.add(MenuEntrySwitchSync(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: model.tabTooltip(i),
|
||||
getter: () async {
|
||||
return model.isVisible[i];
|
||||
},
|
||||
text: model.tabTooltip(tabIndex),
|
||||
currentValue: model.isVisibleEnabled[tabIndex],
|
||||
setter: (show) async {
|
||||
model.setTabVisible(i, show);
|
||||
model.setTabVisible(tabIndex, show);
|
||||
cancelFunc();
|
||||
}));
|
||||
}
|
||||
@ -434,7 +442,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
model.setMultiSelectionMode(false);
|
||||
showToast(translate('Successful'));
|
||||
},
|
||||
child: Icon(model.icons[PeerTabIndex.fav.index]),
|
||||
child: Icon(PeerTabModel.icons[PeerTabIndex.fav.index]),
|
||||
).marginOnly(left: isMobile ? 11 : 6),
|
||||
);
|
||||
}
|
||||
@ -455,7 +463,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
addPeersToAbDialog(peers);
|
||||
model.setMultiSelectionMode(false);
|
||||
},
|
||||
child: Icon(model.icons[PeerTabIndex.ab.index]),
|
||||
child: Icon(PeerTabModel.icons[PeerTabIndex.ab.index]),
|
||||
).marginOnly(left: isMobile ? 11 : 6),
|
||||
);
|
||||
}
|
||||
@ -563,7 +571,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final leftIconSize = Theme.of(context).iconTheme.size ?? 24;
|
||||
final leftActionsSize =
|
||||
(leftIconSize + (4 + 4) * 2) * model.visibleIndexs.length;
|
||||
(leftIconSize + (4 + 4) * 2) * model.visibleEnabledOrderedIndexs.length;
|
||||
final availableWidth = screenWidth - 10 * 2 - leftActionsSize - 2 * 2;
|
||||
final searchWidth = 120;
|
||||
final otherActionWidth = 18 + 10;
|
||||
|
@ -568,6 +568,47 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// Compatible with MenuEntrySwitch, it uses value instead of getter
|
||||
class MenuEntrySwitchSync<T> extends MenuEntrySwitchBase<T> {
|
||||
final SwitchSetter setter;
|
||||
final RxBool _curOption = false.obs;
|
||||
|
||||
MenuEntrySwitchSync({
|
||||
required SwitchType switchType,
|
||||
required String text,
|
||||
required bool currentValue,
|
||||
required this.setter,
|
||||
Rx<TextStyle>? textStyle,
|
||||
EdgeInsets? padding,
|
||||
dismissOnClicked = false,
|
||||
RxBool? enabled,
|
||||
dismissCallback,
|
||||
}) : super(
|
||||
switchType: switchType,
|
||||
text: text,
|
||||
textStyle: textStyle,
|
||||
padding: padding,
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
enabled: enabled,
|
||||
dismissCallback: dismissCallback,
|
||||
) {
|
||||
_curOption.value = currentValue;
|
||||
}
|
||||
|
||||
@override
|
||||
RxBool get curOption => _curOption;
|
||||
@override
|
||||
setOption(bool? option) async {
|
||||
if (option != null) {
|
||||
await setter(option);
|
||||
// Notice: no ensure with getter, best used on menus that are destroyed on click
|
||||
if (_curOption.value != option) {
|
||||
_curOption.value = option;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef Switch2Getter = RxBool Function();
|
||||
typedef Switch2Setter = Future<void> Function(bool);
|
||||
|
||||
|
@ -21,24 +21,43 @@ class PeerTabModel with ChangeNotifier {
|
||||
WeakReference<FFI> parent;
|
||||
int get currentTab => _currentTab;
|
||||
int _currentTab = 0; // index in tabNames
|
||||
List<String> tabNames = [
|
||||
static const int maxTabCount = 5;
|
||||
static const String kPeerTabIndex = 'peer-tab-index';
|
||||
static const String kPeerTabOrder = 'peer-tab-order';
|
||||
static const String kPeerTabVisible = 'peer-tab-visible';
|
||||
static const List<String> tabNames = [
|
||||
'Recent sessions',
|
||||
'Favorites',
|
||||
if (!isWeb) 'Discovered',
|
||||
if (!(bind.isDisableAb() || bind.isDisableAccount())) 'Address book',
|
||||
if (!bind.isDisableAccount()) 'Group',
|
||||
'Discovered',
|
||||
'Address book',
|
||||
'Group',
|
||||
];
|
||||
final List<IconData> icons = [
|
||||
static const List<IconData> icons = [
|
||||
Icons.access_time_filled,
|
||||
Icons.star,
|
||||
if (!isWeb) Icons.explore,
|
||||
if (!(bind.isDisableAb() || bind.isDisableAccount())) IconFont.addressBook,
|
||||
if (!bind.isDisableAccount()) Icons.group,
|
||||
Icons.explore,
|
||||
IconFont.addressBook,
|
||||
Icons.group,
|
||||
];
|
||||
final List<bool> _isVisible = List.filled(5, true, growable: false);
|
||||
List<bool> get isVisible => _isVisible;
|
||||
List<int> get indexs => List.generate(tabNames.length, (index) => index);
|
||||
List<int> get visibleIndexs => indexs.where((e) => _isVisible[e]).toList();
|
||||
List<bool> isEnabled = List.from([
|
||||
true,
|
||||
true,
|
||||
!isWeb,
|
||||
!(bind.isDisableAb() || bind.isDisableAccount()),
|
||||
!bind.isDisableAccount(),
|
||||
]);
|
||||
final List<bool> _isVisible = List.filled(maxTabCount, true, growable: false);
|
||||
List<bool> get isVisibleEnabled => () {
|
||||
final list = _isVisible.toList();
|
||||
for (int i = 0; i < maxTabCount; i++) {
|
||||
list[i] = list[i] && isEnabled[i];
|
||||
}
|
||||
return list;
|
||||
}();
|
||||
final List<int> orders =
|
||||
List.generate(maxTabCount, (index) => index, growable: false);
|
||||
List<int> get visibleEnabledOrderedIndexs =>
|
||||
orders.where((e) => isVisibleEnabled[e]).toList();
|
||||
List<Peer> _selectedPeers = List.empty(growable: true);
|
||||
List<Peer> get selectedPeers => _selectedPeers;
|
||||
bool _multiSelectionMode = false;
|
||||
@ -53,7 +72,7 @@ class PeerTabModel with ChangeNotifier {
|
||||
PeerTabModel(this.parent) {
|
||||
// visible
|
||||
try {
|
||||
final option = bind.getLocalFlutterOption(k: 'peer-tab-visible');
|
||||
final option = bind.getLocalFlutterOption(k: kPeerTabVisible);
|
||||
if (option.isNotEmpty) {
|
||||
List<dynamic> decodeList = jsonDecode(option);
|
||||
if (decodeList.length == _isVisible.length) {
|
||||
@ -67,13 +86,37 @@ class PeerTabModel with ChangeNotifier {
|
||||
} catch (e) {
|
||||
debugPrint("failed to get peer tab visible list:$e");
|
||||
}
|
||||
// order
|
||||
try {
|
||||
final option = bind.getLocalFlutterOption(k: kPeerTabOrder);
|
||||
if (option.isNotEmpty) {
|
||||
List<dynamic> decodeList = jsonDecode(option);
|
||||
if (decodeList.length == maxTabCount) {
|
||||
var sortedList = decodeList.toList();
|
||||
sortedList.sort();
|
||||
bool valid = true;
|
||||
for (int i = 0; i < maxTabCount; i++) {
|
||||
if (sortedList[i] is! int || sortedList[i] != i) {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
if (valid) {
|
||||
for (int i = 0; i < orders.length; i++) {
|
||||
orders[i] = decodeList[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("failed to get peer tab order list: $e");
|
||||
}
|
||||
// init currentTab
|
||||
_currentTab =
|
||||
int.tryParse(bind.getLocalFlutterOption(k: 'peer-tab-index')) ?? 0;
|
||||
if (_currentTab < 0 || _currentTab >= tabNames.length) {
|
||||
int.tryParse(bind.getLocalFlutterOption(k: kPeerTabIndex)) ?? 0;
|
||||
if (_currentTab < 0 || _currentTab >= maxTabCount) {
|
||||
_currentTab = 0;
|
||||
}
|
||||
_trySetCurrentTabToFirstVisible();
|
||||
_trySetCurrentTabToFirstVisibleEnabled();
|
||||
}
|
||||
|
||||
setCurrentTab(int index) {
|
||||
@ -87,15 +130,13 @@ class PeerTabModel with ChangeNotifier {
|
||||
if (index >= 0 && index < tabNames.length) {
|
||||
return translate(tabNames[index]);
|
||||
}
|
||||
assert(false);
|
||||
return index.toString();
|
||||
}
|
||||
|
||||
IconData tabIcon(int index) {
|
||||
if (index >= 0 && index < tabNames.length) {
|
||||
if (index >= 0 && index < icons.length) {
|
||||
return icons[index];
|
||||
}
|
||||
assert(false);
|
||||
return Icons.help;
|
||||
}
|
||||
|
||||
@ -171,29 +212,54 @@ class PeerTabModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
setTabVisible(int index, bool visible) {
|
||||
if (index >= 0 && index < _isVisible.length) {
|
||||
if (index >= 0 && index < maxTabCount) {
|
||||
if (_isVisible[index] != visible) {
|
||||
_isVisible[index] = visible;
|
||||
if (index == _currentTab && !visible) {
|
||||
_trySetCurrentTabToFirstVisible();
|
||||
} else if (visible && visibleIndexs.length == 1) {
|
||||
_trySetCurrentTabToFirstVisibleEnabled();
|
||||
} else if (visible && visibleEnabledOrderedIndexs.length == 1) {
|
||||
_currentTab = index;
|
||||
}
|
||||
try {
|
||||
bind.setLocalFlutterOption(
|
||||
k: 'peer-tab-visible', v: jsonEncode(_isVisible));
|
||||
k: kPeerTabVisible, v: jsonEncode(_isVisible));
|
||||
} catch (_) {}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_trySetCurrentTabToFirstVisible() {
|
||||
if (!_isVisible[_currentTab]) {
|
||||
int firstVisible = _isVisible.indexWhere((e) => e);
|
||||
if (firstVisible >= 0) {
|
||||
_currentTab = firstVisible;
|
||||
_trySetCurrentTabToFirstVisibleEnabled() {
|
||||
if (!visibleEnabledOrderedIndexs.contains(_currentTab)) {
|
||||
if (visibleEnabledOrderedIndexs.isNotEmpty) {
|
||||
_currentTab = visibleEnabledOrderedIndexs.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reorder(int oldIndex, int newIndex) {
|
||||
if (oldIndex < newIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
if (oldIndex < 0 || oldIndex >= visibleEnabledOrderedIndexs.length) {
|
||||
return;
|
||||
}
|
||||
if (newIndex < 0 || newIndex >= visibleEnabledOrderedIndexs.length) {
|
||||
return;
|
||||
}
|
||||
final oldTabValue = visibleEnabledOrderedIndexs[oldIndex];
|
||||
final newTabValue = visibleEnabledOrderedIndexs[newIndex];
|
||||
int oldValueIndex = orders.indexOf(oldTabValue);
|
||||
int newValueIndex = orders.indexOf(newTabValue);
|
||||
final list = orders.toList();
|
||||
if (oldIndex != -1 && newIndex != -1) {
|
||||
list.removeAt(oldValueIndex);
|
||||
list.insert(newValueIndex, oldTabValue);
|
||||
for (int i = 0; i < list.length; i++) {
|
||||
orders[i] = list[i];
|
||||
}
|
||||
bind.setLocalFlutterOption(k: kPeerTabOrder, v: jsonEncode(orders));
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user