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) {
|
Widget _createSwitchBar(BuildContext context) {
|
||||||
final model = Provider.of<PeerTabModel>(context);
|
final model = Provider.of<PeerTabModel>(context);
|
||||||
|
var counter = -1;
|
||||||
return ListView(
|
return ReorderableListView(
|
||||||
|
buildDefaultDragHandles: false,
|
||||||
|
onReorder: model.reorder,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
physics: NeverScrollableScrollPhysics(),
|
physics: NeverScrollableScrollPhysics(),
|
||||||
children: model.visibleIndexs.map((t) {
|
children: model.visibleEnabledOrderedIndexs.map((t) {
|
||||||
final selected = model.currentTab == t;
|
final selected = model.currentTab == t;
|
||||||
final color = selected
|
final color = selected
|
||||||
? MyTheme.tabbar(context).selectedTextColor
|
? MyTheme.tabbar(context).selectedTextColor
|
||||||
@ -155,43 +157,47 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(width: 2, color: color!),
|
bottom: BorderSide(width: 2, color: color!),
|
||||||
));
|
));
|
||||||
return Obx(() => Tooltip(
|
counter += 1;
|
||||||
preferBelow: false,
|
return ReorderableDragStartListener(
|
||||||
message: model.tabTooltip(t),
|
key: ValueKey(t),
|
||||||
onTriggered: isMobile ? mobileShowTabVisibilityMenu : null,
|
index: counter,
|
||||||
child: InkWell(
|
child: Obx(() => Tooltip(
|
||||||
child: Container(
|
preferBelow: false,
|
||||||
decoration: (hover.value
|
message: model.tabTooltip(t),
|
||||||
? (selected ? decoBorder : deco)
|
onTriggered: isMobile ? mobileShowTabVisibilityMenu : null,
|
||||||
: (selected ? decoBorder : null)),
|
child: InkWell(
|
||||||
child: Icon(model.tabIcon(t), color: color)
|
child: Container(
|
||||||
.paddingSymmetric(horizontal: 4),
|
decoration: (hover.value
|
||||||
).paddingSymmetric(horizontal: 4),
|
? (selected ? decoBorder : deco)
|
||||||
onTap: () async {
|
: (selected ? decoBorder : null)),
|
||||||
await handleTabSelection(t);
|
child: Icon(model.tabIcon(t), color: color)
|
||||||
await bind.setLocalFlutterOption(
|
.paddingSymmetric(horizontal: 4),
|
||||||
k: 'peer-tab-index', v: t.toString());
|
).paddingSymmetric(horizontal: 4),
|
||||||
},
|
onTap: () async {
|
||||||
onHover: (value) => hover.value = value,
|
await handleTabSelection(t);
|
||||||
),
|
await bind.setLocalFlutterOption(
|
||||||
));
|
k: PeerTabModel.kPeerTabIndex, v: t.toString());
|
||||||
|
},
|
||||||
|
onHover: (value) => hover.value = value,
|
||||||
|
),
|
||||||
|
)));
|
||||||
}).toList());
|
}).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _createPeersView() {
|
Widget _createPeersView() {
|
||||||
final model = Provider.of<PeerTabModel>(context);
|
final model = Provider.of<PeerTabModel>(context);
|
||||||
Widget child;
|
Widget child;
|
||||||
if (model.visibleIndexs.isEmpty) {
|
if (model.visibleEnabledOrderedIndexs.isEmpty) {
|
||||||
child = visibleContextMenuListener(Row(
|
child = visibleContextMenuListener(Row(
|
||||||
children: [Expanded(child: InkWell())],
|
children: [Expanded(child: InkWell())],
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
if (model.visibleIndexs.contains(model.currentTab)) {
|
if (model.visibleEnabledOrderedIndexs.contains(model.currentTab)) {
|
||||||
child = entries[model.currentTab].widget;
|
child = entries[model.currentTab].widget;
|
||||||
} else {
|
} else {
|
||||||
debugPrint("should not happen! currentTab not in visibleIndexs");
|
debugPrint("should not happen! currentTab not in visibleIndexs");
|
||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
model.setCurrentTab(model.indexs[0]);
|
model.setCurrentTab(model.visibleEnabledOrderedIndexs[0]);
|
||||||
});
|
});
|
||||||
child = entries[0].widget;
|
child = entries[0].widget;
|
||||||
}
|
}
|
||||||
@ -255,16 +261,17 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
void mobileShowTabVisibilityMenu() {
|
void mobileShowTabVisibilityMenu() {
|
||||||
final model = gFFI.peerTabModel;
|
final model = gFFI.peerTabModel;
|
||||||
final items = List<PopupMenuItem>.empty(growable: true);
|
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(
|
items.add(PopupMenuItem(
|
||||||
height: kMinInteractiveDimension * 0.8,
|
height: kMinInteractiveDimension * 0.8,
|
||||||
onTap: () => model.setTabVisible(i, !model.isVisible[i]),
|
onTap: () => model.setTabVisible(i, !model.isVisibleEnabled[i]),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Checkbox(
|
Checkbox(
|
||||||
value: model.isVisible[i],
|
value: model.isVisibleEnabled[i],
|
||||||
onChanged: (_) {
|
onChanged: (_) {
|
||||||
model.setTabVisible(i, !model.isVisible[i]);
|
model.setTabVisible(i, !model.isVisibleEnabled[i]);
|
||||||
if (Navigator.canPop(context)) {
|
if (Navigator.canPop(context)) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
@ -314,16 +321,17 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
|
|
||||||
Widget visibleContextMenu(CancelFunc cancelFunc) {
|
Widget visibleContextMenu(CancelFunc cancelFunc) {
|
||||||
final model = Provider.of<PeerTabModel>(context);
|
final model = Provider.of<PeerTabModel>(context);
|
||||||
final menu = List<MenuEntrySwitch>.empty(growable: true);
|
final menu = List<MenuEntrySwitchSync>.empty(growable: true);
|
||||||
for (int i = 0; i < model.tabNames.length; i++) {
|
for (int i = 0; i < model.orders.length; i++) {
|
||||||
menu.add(MenuEntrySwitch(
|
int tabIndex = model.orders[i];
|
||||||
|
if (tabIndex < 0 || tabIndex >= PeerTabModel.maxTabCount) continue;
|
||||||
|
if (!model.isEnabled[tabIndex]) continue;
|
||||||
|
menu.add(MenuEntrySwitchSync(
|
||||||
switchType: SwitchType.scheckbox,
|
switchType: SwitchType.scheckbox,
|
||||||
text: model.tabTooltip(i),
|
text: model.tabTooltip(tabIndex),
|
||||||
getter: () async {
|
currentValue: model.isVisibleEnabled[tabIndex],
|
||||||
return model.isVisible[i];
|
|
||||||
},
|
|
||||||
setter: (show) async {
|
setter: (show) async {
|
||||||
model.setTabVisible(i, show);
|
model.setTabVisible(tabIndex, show);
|
||||||
cancelFunc();
|
cancelFunc();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -434,7 +442,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
model.setMultiSelectionMode(false);
|
model.setMultiSelectionMode(false);
|
||||||
showToast(translate('Successful'));
|
showToast(translate('Successful'));
|
||||||
},
|
},
|
||||||
child: Icon(model.icons[PeerTabIndex.fav.index]),
|
child: Icon(PeerTabModel.icons[PeerTabIndex.fav.index]),
|
||||||
).marginOnly(left: isMobile ? 11 : 6),
|
).marginOnly(left: isMobile ? 11 : 6),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -455,7 +463,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
addPeersToAbDialog(peers);
|
addPeersToAbDialog(peers);
|
||||||
model.setMultiSelectionMode(false);
|
model.setMultiSelectionMode(false);
|
||||||
},
|
},
|
||||||
child: Icon(model.icons[PeerTabIndex.ab.index]),
|
child: Icon(PeerTabModel.icons[PeerTabIndex.ab.index]),
|
||||||
).marginOnly(left: isMobile ? 11 : 6),
|
).marginOnly(left: isMobile ? 11 : 6),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -563,7 +571,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
final leftIconSize = Theme.of(context).iconTheme.size ?? 24;
|
final leftIconSize = Theme.of(context).iconTheme.size ?? 24;
|
||||||
final leftActionsSize =
|
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 availableWidth = screenWidth - 10 * 2 - leftActionsSize - 2 * 2;
|
||||||
final searchWidth = 120;
|
final searchWidth = 120;
|
||||||
final otherActionWidth = 18 + 10;
|
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 Switch2Getter = RxBool Function();
|
||||||
typedef Switch2Setter = Future<void> Function(bool);
|
typedef Switch2Setter = Future<void> Function(bool);
|
||||||
|
|
||||||
|
@ -21,24 +21,43 @@ class PeerTabModel with ChangeNotifier {
|
|||||||
WeakReference<FFI> parent;
|
WeakReference<FFI> parent;
|
||||||
int get currentTab => _currentTab;
|
int get currentTab => _currentTab;
|
||||||
int _currentTab = 0; // index in tabNames
|
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',
|
'Recent sessions',
|
||||||
'Favorites',
|
'Favorites',
|
||||||
if (!isWeb) 'Discovered',
|
'Discovered',
|
||||||
if (!(bind.isDisableAb() || bind.isDisableAccount())) 'Address book',
|
'Address book',
|
||||||
if (!bind.isDisableAccount()) 'Group',
|
'Group',
|
||||||
];
|
];
|
||||||
final List<IconData> icons = [
|
static const List<IconData> icons = [
|
||||||
Icons.access_time_filled,
|
Icons.access_time_filled,
|
||||||
Icons.star,
|
Icons.star,
|
||||||
if (!isWeb) Icons.explore,
|
Icons.explore,
|
||||||
if (!(bind.isDisableAb() || bind.isDisableAccount())) IconFont.addressBook,
|
IconFont.addressBook,
|
||||||
if (!bind.isDisableAccount()) Icons.group,
|
Icons.group,
|
||||||
];
|
];
|
||||||
final List<bool> _isVisible = List.filled(5, true, growable: false);
|
List<bool> isEnabled = List.from([
|
||||||
List<bool> get isVisible => _isVisible;
|
true,
|
||||||
List<int> get indexs => List.generate(tabNames.length, (index) => index);
|
true,
|
||||||
List<int> get visibleIndexs => indexs.where((e) => _isVisible[e]).toList();
|
!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> _selectedPeers = List.empty(growable: true);
|
||||||
List<Peer> get selectedPeers => _selectedPeers;
|
List<Peer> get selectedPeers => _selectedPeers;
|
||||||
bool _multiSelectionMode = false;
|
bool _multiSelectionMode = false;
|
||||||
@ -53,7 +72,7 @@ class PeerTabModel with ChangeNotifier {
|
|||||||
PeerTabModel(this.parent) {
|
PeerTabModel(this.parent) {
|
||||||
// visible
|
// visible
|
||||||
try {
|
try {
|
||||||
final option = bind.getLocalFlutterOption(k: 'peer-tab-visible');
|
final option = bind.getLocalFlutterOption(k: kPeerTabVisible);
|
||||||
if (option.isNotEmpty) {
|
if (option.isNotEmpty) {
|
||||||
List<dynamic> decodeList = jsonDecode(option);
|
List<dynamic> decodeList = jsonDecode(option);
|
||||||
if (decodeList.length == _isVisible.length) {
|
if (decodeList.length == _isVisible.length) {
|
||||||
@ -67,13 +86,37 @@ class PeerTabModel with ChangeNotifier {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("failed to get peer tab visible list:$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
|
// init currentTab
|
||||||
_currentTab =
|
_currentTab =
|
||||||
int.tryParse(bind.getLocalFlutterOption(k: 'peer-tab-index')) ?? 0;
|
int.tryParse(bind.getLocalFlutterOption(k: kPeerTabIndex)) ?? 0;
|
||||||
if (_currentTab < 0 || _currentTab >= tabNames.length) {
|
if (_currentTab < 0 || _currentTab >= maxTabCount) {
|
||||||
_currentTab = 0;
|
_currentTab = 0;
|
||||||
}
|
}
|
||||||
_trySetCurrentTabToFirstVisible();
|
_trySetCurrentTabToFirstVisibleEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentTab(int index) {
|
setCurrentTab(int index) {
|
||||||
@ -87,15 +130,13 @@ class PeerTabModel with ChangeNotifier {
|
|||||||
if (index >= 0 && index < tabNames.length) {
|
if (index >= 0 && index < tabNames.length) {
|
||||||
return translate(tabNames[index]);
|
return translate(tabNames[index]);
|
||||||
}
|
}
|
||||||
assert(false);
|
|
||||||
return index.toString();
|
return index.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
IconData tabIcon(int index) {
|
IconData tabIcon(int index) {
|
||||||
if (index >= 0 && index < tabNames.length) {
|
if (index >= 0 && index < icons.length) {
|
||||||
return icons[index];
|
return icons[index];
|
||||||
}
|
}
|
||||||
assert(false);
|
|
||||||
return Icons.help;
|
return Icons.help;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,29 +212,54 @@ class PeerTabModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTabVisible(int index, bool visible) {
|
setTabVisible(int index, bool visible) {
|
||||||
if (index >= 0 && index < _isVisible.length) {
|
if (index >= 0 && index < maxTabCount) {
|
||||||
if (_isVisible[index] != visible) {
|
if (_isVisible[index] != visible) {
|
||||||
_isVisible[index] = visible;
|
_isVisible[index] = visible;
|
||||||
if (index == _currentTab && !visible) {
|
if (index == _currentTab && !visible) {
|
||||||
_trySetCurrentTabToFirstVisible();
|
_trySetCurrentTabToFirstVisibleEnabled();
|
||||||
} else if (visible && visibleIndexs.length == 1) {
|
} else if (visible && visibleEnabledOrderedIndexs.length == 1) {
|
||||||
_currentTab = index;
|
_currentTab = index;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
bind.setLocalFlutterOption(
|
bind.setLocalFlutterOption(
|
||||||
k: 'peer-tab-visible', v: jsonEncode(_isVisible));
|
k: kPeerTabVisible, v: jsonEncode(_isVisible));
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_trySetCurrentTabToFirstVisible() {
|
_trySetCurrentTabToFirstVisibleEnabled() {
|
||||||
if (!_isVisible[_currentTab]) {
|
if (!visibleEnabledOrderedIndexs.contains(_currentTab)) {
|
||||||
int firstVisible = _isVisible.indexWhere((e) => e);
|
if (visibleEnabledOrderedIndexs.isNotEmpty) {
|
||||||
if (firstVisible >= 0) {
|
_currentTab = visibleEnabledOrderedIndexs.first;
|
||||||
_currentTab = firstVisible;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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