2023-06-23 16:23:46 -04:00
|
|
|
import 'dart:async';
|
|
|
|
|
2023-10-21 08:02:28 -04:00
|
|
|
import 'package:flutter/material.dart';
|
2023-10-03 13:00:59 -04:00
|
|
|
import 'package:recon/apis/record_api.dart';
|
|
|
|
import 'package:recon/clients/api_client.dart';
|
|
|
|
import 'package:recon/models/inventory/resonite_directory.dart';
|
|
|
|
import 'package:recon/models/records/record.dart';
|
2023-10-21 08:02:28 -04:00
|
|
|
|
|
|
|
enum SortMode {
|
|
|
|
name,
|
2023-11-20 17:12:30 -04:00
|
|
|
date,
|
|
|
|
resonite;
|
2023-10-21 08:02:28 -04:00
|
|
|
|
|
|
|
int sortFunction(Record a, Record b, {bool reverse = false}) {
|
|
|
|
final func = switch (this) {
|
|
|
|
SortMode.name => (Record x, Record y) =>
|
|
|
|
x.formattedName.toString().toLowerCase().compareTo(y.formattedName.toString().toLowerCase()),
|
|
|
|
SortMode.date => (Record x, Record y) => x.creationTime.compareTo(y.creationTime),
|
2023-11-20 17:12:30 -04:00
|
|
|
SortMode.resonite => (Record x, Record y) => x.isItem
|
|
|
|
? x.creationTime.compareTo(y.creationTime)
|
|
|
|
: x.formattedName.toString().toLowerCase().compareTo(y.formattedName.toString().toLowerCase()),
|
2023-10-21 08:02:28 -04:00
|
|
|
};
|
|
|
|
if (reverse) {
|
|
|
|
return func(b, a);
|
|
|
|
}
|
|
|
|
return func(a, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const Map<SortMode, IconData> _iconsMap = {
|
|
|
|
SortMode.name: Icons.sort_by_alpha,
|
2023-11-20 17:12:30 -04:00
|
|
|
SortMode.date: Icons.access_time_outlined,
|
|
|
|
SortMode.resonite: Icons.star_border_purple500_sharp,
|
2023-10-21 08:02:28 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
IconData get icon => _iconsMap[this] ?? Icons.question_mark;
|
|
|
|
}
|
2023-06-17 10:58:32 -04:00
|
|
|
|
|
|
|
class InventoryClient extends ChangeNotifier {
|
|
|
|
final ApiClient apiClient;
|
2023-10-21 08:02:28 -04:00
|
|
|
final Map<String, Record> _selectedRecords = {};
|
2023-06-17 10:58:32 -04:00
|
|
|
|
2023-09-30 06:22:32 -04:00
|
|
|
Future<ResoniteDirectory>? _currentDirectory;
|
2023-11-20 17:12:30 -04:00
|
|
|
SortMode _sortMode = SortMode.resonite;
|
2023-10-21 08:02:28 -04:00
|
|
|
bool _sortReverse = false;
|
2023-06-17 10:58:32 -04:00
|
|
|
|
|
|
|
InventoryClient({required this.apiClient});
|
|
|
|
|
2023-10-21 08:02:28 -04:00
|
|
|
SortMode get sortMode => _sortMode;
|
|
|
|
|
|
|
|
bool get sortReverse => _sortReverse;
|
|
|
|
|
|
|
|
set sortMode(SortMode mode) {
|
|
|
|
if (_sortMode != mode) {
|
|
|
|
_sortMode = mode;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
set sortReverse(bool reverse) {
|
|
|
|
if (_sortReverse != reverse) {
|
|
|
|
_sortReverse = reverse;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
2023-06-23 16:23:46 -04:00
|
|
|
|
|
|
|
List<Record> get selectedRecords => _selectedRecords.values.toList();
|
|
|
|
|
2023-10-21 08:02:28 -04:00
|
|
|
Future<ResoniteDirectory>? get directoryFuture => _currentDirectory?.then(
|
|
|
|
(ResoniteDirectory value) {
|
|
|
|
value.children.sort(
|
2023-11-01 10:10:43 -04:00
|
|
|
(ResoniteDirectory a, ResoniteDirectory b) =>
|
|
|
|
_sortMode.sortFunction(a.record, b.record, reverse: _sortReverse),
|
2023-10-21 08:02:28 -04:00
|
|
|
);
|
|
|
|
return value;
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-06-23 16:23:46 -04:00
|
|
|
bool get isAnyRecordSelected => _selectedRecords.isNotEmpty;
|
|
|
|
|
|
|
|
bool isRecordSelected(Record record) => _selectedRecords.containsKey(record.id);
|
|
|
|
|
|
|
|
int get selectedRecordCount => _selectedRecords.length;
|
|
|
|
|
|
|
|
bool get onlyFilesSelected => _selectedRecords.values
|
|
|
|
.every((element) => element.recordType != RecordType.link && element.recordType != RecordType.directory);
|
|
|
|
|
|
|
|
void clearSelectedRecords() {
|
|
|
|
_selectedRecords.clear();
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> deleteSelectedRecords() async {
|
|
|
|
for (final recordId in _selectedRecords.keys) {
|
|
|
|
await RecordApi.deleteRecord(apiClient, recordId: recordId);
|
|
|
|
}
|
|
|
|
_selectedRecords.clear();
|
|
|
|
reloadCurrentDirectory();
|
|
|
|
}
|
|
|
|
|
|
|
|
void toggleRecordSelected(Record record) {
|
|
|
|
if (_selectedRecords.containsKey(record.id)) {
|
|
|
|
_selectedRecords.remove(record.id);
|
|
|
|
} else {
|
|
|
|
_selectedRecords[record.id] = record;
|
|
|
|
}
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
2023-06-17 10:58:32 -04:00
|
|
|
Future<List<Record>> _getDirectory(Record record) async {
|
2023-09-30 06:22:32 -04:00
|
|
|
ResoniteDirectory? dir;
|
2023-06-17 11:36:52 -04:00
|
|
|
try {
|
|
|
|
dir = await _currentDirectory;
|
2023-06-23 16:23:46 -04:00
|
|
|
} catch (_) {}
|
2023-06-17 10:58:32 -04:00
|
|
|
final List<Record> records;
|
|
|
|
if (dir == null || record.isRoot) {
|
|
|
|
records = await RecordApi.getUserRecordsAt(
|
|
|
|
apiClient,
|
2023-09-30 06:22:32 -04:00
|
|
|
path: ResoniteDirectory.rootName,
|
2023-06-17 10:58:32 -04:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
if (record.recordType == RecordType.link) {
|
2023-11-01 10:10:43 -04:00
|
|
|
if (record.isGroupRecord) {
|
|
|
|
final linkRecord = await RecordApi.getGroupRecordByPath(
|
|
|
|
apiClient,
|
|
|
|
path: "root/${record.path}/${record.name}",
|
|
|
|
groupId: record.linkOwnerId,
|
|
|
|
);
|
|
|
|
records = await RecordApi.getGroupRecordsAt(
|
|
|
|
apiClient,
|
|
|
|
path: "${linkRecord.path}\\${linkRecord.name}",
|
|
|
|
groupId: linkRecord.ownerId,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
final linkRecord = await RecordApi.getUserRecord(
|
|
|
|
apiClient,
|
|
|
|
recordId: record.linkRecordId,
|
|
|
|
user: record.linkOwnerId,
|
|
|
|
);
|
|
|
|
records = await RecordApi.getUserRecordsAt(
|
|
|
|
apiClient,
|
|
|
|
path: "${linkRecord.path}\\${linkRecord.name}",
|
|
|
|
user: linkRecord.ownerId,
|
|
|
|
);
|
|
|
|
}
|
2023-06-17 10:58:32 -04:00
|
|
|
} else {
|
2023-06-23 16:23:46 -04:00
|
|
|
records =
|
|
|
|
await RecordApi.getUserRecordsAt(apiClient, path: "${record.path}\\${record.name}", user: record.ownerId);
|
2023-06-17 10:58:32 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return records;
|
|
|
|
}
|
|
|
|
|
|
|
|
void loadInventoryRoot() {
|
|
|
|
final rootRecord = Record.inventoryRoot();
|
|
|
|
final rootFuture = _getDirectory(rootRecord).then(
|
|
|
|
(records) {
|
2023-09-30 06:22:32 -04:00
|
|
|
final rootDir = ResoniteDirectory(
|
2023-06-17 10:58:32 -04:00
|
|
|
record: rootRecord,
|
|
|
|
children: [],
|
|
|
|
);
|
|
|
|
rootDir.children.addAll(
|
2023-09-30 06:22:32 -04:00
|
|
|
records.map((e) => ResoniteDirectory.fromRecord(record: e, parent: rootDir)).toList(),
|
2023-06-17 10:58:32 -04:00
|
|
|
);
|
|
|
|
return rootDir;
|
|
|
|
},
|
|
|
|
);
|
|
|
|
_currentDirectory = rootFuture;
|
2023-06-23 16:23:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void forceNotify() => notifyListeners();
|
|
|
|
|
|
|
|
Future<void> reloadCurrentDirectory() async {
|
|
|
|
final dir = await _currentDirectory;
|
|
|
|
|
|
|
|
if (dir == null) {
|
|
|
|
throw "Failed to reload: No directory loaded.";
|
|
|
|
}
|
|
|
|
|
|
|
|
_currentDirectory = _getDirectory(dir.record).then(
|
|
|
|
(records) {
|
2023-09-30 06:22:32 -04:00
|
|
|
final children = records.map((record) => ResoniteDirectory.fromRecord(record: record, parent: dir)).toList();
|
|
|
|
final newDir = ResoniteDirectory(record: dir.record, children: children, parent: dir.parent);
|
2023-06-23 16:23:46 -04:00
|
|
|
|
|
|
|
final parentIdx = dir.parent?.children.indexOf(dir) ?? -1;
|
|
|
|
if (parentIdx != -1) {
|
|
|
|
dir.parent?.children[parentIdx] = newDir;
|
|
|
|
}
|
|
|
|
return newDir;
|
|
|
|
},
|
|
|
|
).onError((error, stackTrace) {
|
|
|
|
return dir;
|
|
|
|
});
|
2023-06-17 10:58:32 -04:00
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> navigateTo(Record record) async {
|
|
|
|
final dir = await _currentDirectory;
|
|
|
|
|
|
|
|
if (dir == null) {
|
|
|
|
throw "Failed to open: No directory loaded.";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (record.recordType != RecordType.directory && record.recordType != RecordType.link) {
|
|
|
|
throw "Failed to open: Record is not a directory.";
|
|
|
|
}
|
|
|
|
|
|
|
|
final childDir = dir.findChildByRecord(record);
|
|
|
|
if (childDir == null) {
|
|
|
|
throw "Failed to open: Record is not a child of current directory.";
|
|
|
|
}
|
|
|
|
|
2023-06-23 16:23:46 -04:00
|
|
|
Object? caughtError;
|
|
|
|
|
2023-06-17 10:58:32 -04:00
|
|
|
if (childDir.isLoaded) {
|
|
|
|
_currentDirectory = Future.value(childDir);
|
|
|
|
} else {
|
|
|
|
_currentDirectory = _getDirectory(record).then(
|
|
|
|
(records) {
|
|
|
|
childDir.children.clear();
|
2023-10-21 08:02:28 -04:00
|
|
|
childDir.children
|
|
|
|
.addAll(records.map((record) => ResoniteDirectory.fromRecord(record: record, parent: childDir)));
|
2023-06-17 10:58:32 -04:00
|
|
|
return childDir;
|
|
|
|
},
|
2023-06-17 13:28:23 -04:00
|
|
|
).onError((error, stackTrace) {
|
2023-06-23 16:23:46 -04:00
|
|
|
caughtError = error;
|
2023-06-17 13:28:23 -04:00
|
|
|
return dir;
|
|
|
|
});
|
2023-06-17 10:58:32 -04:00
|
|
|
}
|
|
|
|
notifyListeners();
|
2023-06-23 16:23:46 -04:00
|
|
|
await _currentDirectory;
|
|
|
|
// Dirty hack to throw the error here instead of letting the FutureBuilder handle it. This means we can keep showing
|
|
|
|
// the previous directory while also being able to display the error as a snackbar.
|
|
|
|
if (caughtError != null) {
|
|
|
|
throw caughtError!;
|
|
|
|
}
|
2023-06-17 10:58:32 -04:00
|
|
|
}
|
|
|
|
|
2023-06-17 11:36:52 -04:00
|
|
|
Future<void> navigateUp({int times = 1}) async {
|
2023-06-23 16:23:46 -04:00
|
|
|
if (times == 0) return;
|
|
|
|
|
2023-06-17 11:36:52 -04:00
|
|
|
var dir = await _currentDirectory;
|
2023-06-17 10:58:32 -04:00
|
|
|
if (dir == null) {
|
|
|
|
throw "Failed to navigate up: No directory loaded.";
|
|
|
|
}
|
|
|
|
if (dir.record.isRoot) {
|
|
|
|
throw "Failed navigate up: Already at root";
|
|
|
|
}
|
|
|
|
|
2023-06-17 11:36:52 -04:00
|
|
|
for (int i = 0; i < times; i++) {
|
|
|
|
dir = dir?.parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
_currentDirectory = Future.value(dir);
|
2023-06-17 10:58:32 -04:00
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|