mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-01-31 08:30:09 -06:00
186 lines
5.9 KiB
Dart
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,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
}
|