peer_tab_model.dart 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import 'dart:convert';
  2. import 'dart:math';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter_hbb/consts.dart';
  5. import 'package:flutter_hbb/models/peer_model.dart';
  6. import 'package:flutter_hbb/models/platform_model.dart';
  7. import 'package:get/get.dart';
  8. import '../common.dart';
  9. import 'model.dart';
  10. enum PeerTabIndex {
  11. recent,
  12. fav,
  13. lan,
  14. ab,
  15. group,
  16. }
  17. class PeerTabModel with ChangeNotifier {
  18. WeakReference<FFI> parent;
  19. int get currentTab => _currentTab;
  20. int _currentTab = 0; // index in tabNames
  21. static const int maxTabCount = 5;
  22. static const List<String> tabNames = [
  23. 'Recent sessions',
  24. 'Favorites',
  25. 'Discovered',
  26. 'Address book',
  27. 'Group',
  28. ];
  29. static const List<IconData> icons = [
  30. Icons.access_time_filled,
  31. Icons.star,
  32. Icons.explore,
  33. IconFont.addressBook,
  34. Icons.group,
  35. ];
  36. List<bool> isEnabled = List.from([
  37. true,
  38. true,
  39. !isWeb,
  40. !(bind.isDisableAb() || bind.isDisableAccount()),
  41. !(bind.isDisableGroupPanel() || bind.isDisableAccount()),
  42. ]);
  43. final List<bool> _isVisible = List.filled(maxTabCount, true, growable: false);
  44. List<bool> get isVisibleEnabled => () {
  45. final list = _isVisible.toList();
  46. for (int i = 0; i < maxTabCount; i++) {
  47. list[i] = list[i] && isEnabled[i];
  48. }
  49. return list;
  50. }();
  51. final List<int> orders =
  52. List.generate(maxTabCount, (index) => index, growable: false);
  53. List<int> get visibleEnabledOrderedIndexs =>
  54. orders.where((e) => isVisibleEnabled[e]).toList();
  55. List<Peer> _selectedPeers = List.empty(growable: true);
  56. List<Peer> get selectedPeers => _selectedPeers;
  57. bool _multiSelectionMode = false;
  58. bool get multiSelectionMode => _multiSelectionMode;
  59. List<Peer> _currentTabCachedPeers = List.empty(growable: true);
  60. List<Peer> get currentTabCachedPeers => _currentTabCachedPeers;
  61. bool _isShiftDown = false;
  62. bool get isShiftDown => _isShiftDown;
  63. String _lastId = '';
  64. String get lastId => _lastId;
  65. PeerTabModel(this.parent) {
  66. // visible
  67. try {
  68. final option = bind.getLocalFlutterOption(k: kOptionPeerTabVisible);
  69. if (option.isNotEmpty) {
  70. List<dynamic> decodeList = jsonDecode(option);
  71. if (decodeList.length == _isVisible.length) {
  72. for (int i = 0; i < _isVisible.length; i++) {
  73. if (decodeList[i] is bool) {
  74. _isVisible[i] = decodeList[i];
  75. }
  76. }
  77. }
  78. }
  79. } catch (e) {
  80. debugPrint("failed to get peer tab visible list:$e");
  81. }
  82. // order
  83. try {
  84. final option = bind.getLocalFlutterOption(k: kOptionPeerTabOrder);
  85. if (option.isNotEmpty) {
  86. List<dynamic> decodeList = jsonDecode(option);
  87. if (decodeList.length == maxTabCount) {
  88. var sortedList = decodeList.toList();
  89. sortedList.sort();
  90. bool valid = true;
  91. for (int i = 0; i < maxTabCount; i++) {
  92. if (sortedList[i] is! int || sortedList[i] != i) {
  93. valid = false;
  94. }
  95. }
  96. if (valid) {
  97. for (int i = 0; i < orders.length; i++) {
  98. orders[i] = decodeList[i];
  99. }
  100. }
  101. }
  102. }
  103. } catch (e) {
  104. debugPrint("failed to get peer tab order list: $e");
  105. }
  106. // init currentTab
  107. _currentTab =
  108. int.tryParse(bind.getLocalFlutterOption(k: kOptionPeerTabIndex)) ?? 0;
  109. if (_currentTab < 0 || _currentTab >= maxTabCount) {
  110. _currentTab = 0;
  111. }
  112. _trySetCurrentTabToFirstVisibleEnabled();
  113. }
  114. setCurrentTab(int index) {
  115. if (_currentTab != index) {
  116. _currentTab = index;
  117. notifyListeners();
  118. }
  119. }
  120. String tabTooltip(int index) {
  121. if (index >= 0 && index < tabNames.length) {
  122. return translate(tabNames[index]);
  123. }
  124. return index.toString();
  125. }
  126. IconData tabIcon(int index) {
  127. if (index >= 0 && index < icons.length) {
  128. return icons[index];
  129. }
  130. return Icons.help;
  131. }
  132. setMultiSelectionMode(bool mode) {
  133. _multiSelectionMode = mode;
  134. if (!mode) {
  135. _selectedPeers.clear();
  136. _lastId = '';
  137. }
  138. notifyListeners();
  139. }
  140. select(Peer peer) {
  141. if (!_multiSelectionMode) {
  142. // https://github.com/flutter/flutter/issues/101275#issuecomment-1604541700
  143. // After onTap, the shift key should be pressed for a while when not in multiselection mode,
  144. // because onTap is delayed when onDoubleTap is not null
  145. if (isDesktop || isWebDesktop) return;
  146. _multiSelectionMode = true;
  147. }
  148. final cached = _currentTabCachedPeers.map((e) => e.id).toList();
  149. int thisIndex = cached.indexOf(peer.id);
  150. int lastIndex = cached.indexOf(_lastId);
  151. if (_isShiftDown && thisIndex >= 0 && lastIndex >= 0) {
  152. int start = min(thisIndex, lastIndex);
  153. int end = max(thisIndex, lastIndex);
  154. bool remove = isPeerSelected(peer.id);
  155. for (var i = start; i <= end; i++) {
  156. if (remove) {
  157. if (isPeerSelected(cached[i])) {
  158. _selectedPeers.removeWhere((p) => p.id == cached[i]);
  159. }
  160. } else {
  161. if (!isPeerSelected(cached[i])) {
  162. _selectedPeers.add(_currentTabCachedPeers[i]);
  163. }
  164. }
  165. }
  166. } else {
  167. if (isPeerSelected(peer.id)) {
  168. _selectedPeers.removeWhere((p) => p.id == peer.id);
  169. } else {
  170. _selectedPeers.add(peer);
  171. }
  172. }
  173. _lastId = peer.id;
  174. notifyListeners();
  175. }
  176. // `notifyListeners()` will cause many rebuilds.
  177. // So, we need to reduce the calls to "notifyListeners()" only when necessary.
  178. // A better way is to use a new model.
  179. setCurrentTabCachedPeers(List<Peer> peers) {
  180. Future.delayed(Duration.zero, () {
  181. final isPreEmpty = _currentTabCachedPeers.isEmpty;
  182. _currentTabCachedPeers = peers;
  183. final isNowEmpty = _currentTabCachedPeers.isEmpty;
  184. if (isPreEmpty != isNowEmpty) {
  185. notifyListeners();
  186. }
  187. });
  188. }
  189. selectAll() {
  190. _selectedPeers = _currentTabCachedPeers.toList();
  191. notifyListeners();
  192. }
  193. bool isPeerSelected(String id) {
  194. return selectedPeers.firstWhereOrNull((p) => p.id == id) != null;
  195. }
  196. setShiftDown(bool v) {
  197. if (_isShiftDown != v) {
  198. _isShiftDown = v;
  199. if (_multiSelectionMode) {
  200. notifyListeners();
  201. }
  202. }
  203. }
  204. setTabVisible(int index, bool visible) {
  205. if (index >= 0 && index < maxTabCount) {
  206. if (_isVisible[index] != visible) {
  207. _isVisible[index] = visible;
  208. if (index == _currentTab && !visible) {
  209. _trySetCurrentTabToFirstVisibleEnabled();
  210. } else if (visible && visibleEnabledOrderedIndexs.length == 1) {
  211. _currentTab = index;
  212. }
  213. try {
  214. bind.setLocalFlutterOption(
  215. k: kOptionPeerTabVisible, v: jsonEncode(_isVisible));
  216. } catch (_) {}
  217. notifyListeners();
  218. }
  219. }
  220. }
  221. _trySetCurrentTabToFirstVisibleEnabled() {
  222. if (!visibleEnabledOrderedIndexs.contains(_currentTab)) {
  223. if (visibleEnabledOrderedIndexs.isNotEmpty) {
  224. _currentTab = visibleEnabledOrderedIndexs.first;
  225. }
  226. }
  227. }
  228. reorder(int oldIndex, int newIndex) {
  229. if (oldIndex < newIndex) {
  230. newIndex -= 1;
  231. }
  232. if (oldIndex < 0 || oldIndex >= visibleEnabledOrderedIndexs.length) {
  233. return;
  234. }
  235. if (newIndex < 0 || newIndex >= visibleEnabledOrderedIndexs.length) {
  236. return;
  237. }
  238. final oldTabValue = visibleEnabledOrderedIndexs[oldIndex];
  239. final newTabValue = visibleEnabledOrderedIndexs[newIndex];
  240. int oldValueIndex = orders.indexOf(oldTabValue);
  241. int newValueIndex = orders.indexOf(newTabValue);
  242. final list = orders.toList();
  243. if (oldIndex != -1 && newIndex != -1) {
  244. list.removeAt(oldValueIndex);
  245. list.insert(newValueIndex, oldTabValue);
  246. for (int i = 0; i < list.length; i++) {
  247. orders[i] = list[i];
  248. }
  249. bind.setLocalFlutterOption(k: kOptionPeerTabOrder, v: jsonEncode(orders));
  250. notifyListeners();
  251. }
  252. }
  253. }