Files
phylum/client/lib/libphylum/explorer/explorer_controller.dart
2024-12-04 14:25:06 +05:30

186 lines
5.9 KiB
Dart

import 'dart:async';
import 'dart:math';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:state_notifier/state_notifier.dart';
import 'page.dart';
import 'explorer_navigator.dart';
import 'selection_mode.dart';
class ExplorerState {
final String? folderId;
final Resource? folder;
final List<Resource> resources;
final Set<String> selectedIds;
final int focusIndex;
final bool showFocus;
final int selectionStartIndex;
final bool dragging;
final bool refreshing;
final bool refreshError;
Resource? get focussed => focusIndex < 0 || focusIndex >= resources.length ? null : resources[focusIndex];
String? get focusId => focussed?.id;
Iterable<Resource> get selected => resources.where((r) => selectedIds.contains(r.id));
Resource? get selectedSingle => selectedIds.length != 1 ? null : resources.where((r) => selectedIds.contains(r.id)).first;
Resource? get focussedIfSelected => selectedIds.isEmpty ? null : focussed;
const ExplorerState({
required this.folderId,
this.folder,
this.resources = const [],
this.selectedIds = const {},
this.focusIndex = -1,
this.showFocus = false,
this.selectionStartIndex = -1,
this.dragging = false,
this.refreshing = false,
this.refreshError = false,
});
ExplorerState copyWith({
Resource? folder,
List<Resource>? resources,
Set<String>? selectedIds,
int? focusIndex,
bool? showFocus,
int? selectionStartIndex,
bool? dragging,
bool? refreshing,
bool? refreshError,
}) =>
ExplorerState(
folderId: folderId,
folder: folder ?? this.folder,
resources: resources ?? this.resources,
selectedIds: selectedIds ?? this.selectedIds,
focusIndex: focusIndex ?? this.focusIndex,
showFocus: showFocus ?? this.showFocus,
selectionStartIndex: selectionStartIndex ?? this.selectionStartIndex,
dragging: dragging ?? this.dragging,
refreshing: refreshing ?? this.refreshing,
refreshError: refreshError ?? this.refreshError,
);
bool isSelected(String id) {
return selectedIds.contains(id);
}
}
class ExplorerController extends StateNotifier<ExplorerState> {
final PhylumAccount _account;
final ExplorerNavigator _navHistoryManager;
late void Function() _removeHistoryManagerListener;
StreamSubscription<Resource?>? _folderSubscription;
StreamSubscription<List<Resource>>? _childrenSubscription;
ExplorerController({
required PhylumAccount account,
required ExplorerNavigator navHistoryManager,
}) : _account = account,
_navHistoryManager = navHistoryManager,
super(ExplorerState(folderId: null)) {
_removeHistoryManagerListener = navHistoryManager.addListener((state) {
updatePage(state.current);
}, fireImmediately: true);
}
@override
void dispose() {
_removeHistoryManagerListener.call();
_folderSubscription?.cancel();
_childrenSubscription?.cancel();
super.dispose();
}
void updatePage(ExplorerPage page) {
final folderId = page is ExplorerPageFolder ? (page).folderId : null;
state = ExplorerState(folderId: folderId);
_folderSubscription?.cancel();
_childrenSubscription?.cancel();
_folderSubscription = page.watchResource(_account).listen((e) => state = state.copyWith(folder: e));
_childrenSubscription = page.watchChildren(_account).listen((e) => _updateResourceList(e));
refresh();
}
void _updateResourceList(List<Resource> resources) {
final extras = Set.of(state.selectedIds);
extras.removeAll(state.resources.map((r) => r.id));
final selectedIds = Set.of(state.selectedIds);
selectedIds.removeAll(extras);
// selection.
final focusIndex = resources.indexWhere((r) => r.id == state.focusId);
state = state.copyWith(
resources: resources,
focusIndex: focusIndex,
selectedIds: selectedIds,
);
}
void setDragging(bool dragging) {
state = state.copyWith(dragging: dragging);
}
void updateSelection(int Function(int)? indexFn, SelectionMode mode, bool highlight) {
Set<String>? selectedIds;
int index = indexFn?.call(state.focusIndex) ?? state.focusIndex;
index = min(state.resources.length - 1, max(0, index));
int selectionStartIndex = state.selectionStartIndex;
switch (mode) {
case SelectionMode.range:
if (selectionStartIndex == -1) {
selectionStartIndex = index;
}
selectedIds = Set.of(state.resources.getRange(min(selectionStartIndex, index), max(selectionStartIndex, index) + 1).map((r) => r.id));
break;
case SelectionMode.single:
selectedIds = {state.resources[index].id};
selectionStartIndex = index;
break;
case SelectionMode.toggle:
selectedIds = Set.of(state.selectedIds);
if (!selectedIds.add(state.resources[index].id)) {
selectedIds.remove(state.resources[index].id);
}
selectionStartIndex = index;
break;
case SelectionMode.add:
selectedIds = Set.of(state.selectedIds);
selectedIds.add(state.resources[index].id);
selectionStartIndex = index;
break;
case SelectionMode.none:
selectedIds = const {};
selectionStartIndex = index;
break;
case SelectionMode.all:
selectedIds = Set.of(state.resources.map((r) => r.id));
case SelectionMode.noChange:
break;
}
state = state.copyWith(
selectedIds: selectedIds,
selectionStartIndex: selectionStartIndex,
focusIndex: index,
showFocus: highlight,
);
}
Future refresh() {
if (state.refreshing) return Future.value(state);
state = state.copyWith(refreshing: true);
return _navHistoryManager.current.refresh(_account).then((success) {
if (mounted) {
state = state.copyWith(
refreshing: false,
refreshError: !success,
);
}
});
}
}