refactored room and file sorting to use direct comparators instead of temporary sort strings, removed obsolete 'sort' properties from API models, and improved file list sorting with case-insensitive name comparisons and null-safe date handling

This commit is contained in:
2026-05-17 00:27:17 +02:00
parent e5873f73b9
commit 215911cf29
8 changed files with 27 additions and 77 deletions
@@ -16,26 +16,20 @@ class GetRoomResponse extends ApiResponse {
Map<String, dynamic> toJson() => _$GetRoomResponseToJson(this); Map<String, dynamic> toJson() => _$GetRoomResponseToJson(this);
List<GetRoomResponseObject> sortBy({ List<GetRoomResponseObject> sortBy({
bool lastActivity = true,
required bool favoritesToTop, required bool favoritesToTop,
required bool unreadToTop, required bool unreadToTop,
}) { }) {
for (var chat in data) { return data.toList()..sort((a, b) {
final buffer = StringBuffer(); if (favoritesToTop && a.isFavorite != b.isFavorite) {
return a.isFavorite ? -1 : 1;
if (favoritesToTop) {
buffer.write(chat.isFavorite ? 'b' : 'a');
} }
if (unreadToTop) { if (unreadToTop) {
buffer.write(chat.unreadMessages > 0 ? 'b' : 'a'); final aUnread = a.unreadMessages > 0;
final bUnread = b.unreadMessages > 0;
if (aUnread != bUnread) return aUnread ? -1 : 1;
} }
return b.lastActivity.compareTo(a.lastActivity);
buffer.write(chat.lastActivity); });
chat.sort = buffer.toString();
}
return data.toList()..sort((a, b) => b.sort!.compareTo(a.sort!));
} }
} }
@@ -71,7 +65,6 @@ class GetRoomResponseObject {
String? status; String? status;
String? statusIcon; String? statusIcon;
String? statusMessage; String? statusMessage;
String? sort;
GetRoomResponseObject( GetRoomResponseObject(
this.id, this.id,
@@ -60,7 +60,7 @@ GetRoomResponseObject _$GetRoomResponseObjectFromJson(
json['status'] as String?, json['status'] as String?,
json['statusIcon'] as String?, json['statusIcon'] as String?,
json['statusMessage'] as String?, json['statusMessage'] as String?,
)..sort = json['sort'] as String?; );
Map<String, dynamic> _$GetRoomResponseObjectToJson( Map<String, dynamic> _$GetRoomResponseObjectToJson(
GetRoomResponseObject instance, GetRoomResponseObject instance,
@@ -97,7 +97,6 @@ Map<String, dynamic> _$GetRoomResponseObjectToJson(
'status': instance.status, 'status': instance.status,
'statusIcon': instance.statusIcon, 'statusIcon': instance.statusIcon,
'statusMessage': instance.statusMessage, 'statusMessage': instance.statusMessage,
'sort': instance.sort,
}; };
const _$GetRoomResponseObjectConversationTypeEnumMap = { const _$GetRoomResponseObjectConversationTypeEnumMap = {
@@ -13,7 +13,6 @@ class CacheableFile {
String? eTag; String? eTag;
DateTime? createdAt; DateTime? createdAt;
DateTime? modifiedAt; DateTime? modifiedAt;
String? sort;
/// Nextcloud's instance-local file id (`oc:fileid`). Used to address the /// Nextcloud's instance-local file id (`oc:fileid`). Used to address the
/// preview API by id, which is more reliable than the path-based variant /// preview API by id, which is more reliable than the path-based variant
@@ -22,7 +22,7 @@ CacheableFile _$CacheableFileFromJson(Map<String, dynamic> json) =>
: DateTime.parse(json['modifiedAt'] as String), : DateTime.parse(json['modifiedAt'] as String),
fileId: (json['fileId'] as num?)?.toInt(), fileId: (json['fileId'] as num?)?.toInt(),
hasPreview: json['hasPreview'] as bool?, hasPreview: json['hasPreview'] as bool?,
)..sort = json['sort'] as String?; );
Map<String, dynamic> _$CacheableFileToJson(CacheableFile instance) => Map<String, dynamic> _$CacheableFileToJson(CacheableFile instance) =>
<String, dynamic>{ <String, dynamic>{
@@ -34,7 +34,6 @@ Map<String, dynamic> _$CacheableFileToJson(CacheableFile instance) =>
'eTag': instance.eTag, 'eTag': instance.eTag,
'createdAt': instance.createdAt?.toIso8601String(), 'createdAt': instance.createdAt?.toIso8601String(),
'modifiedAt': instance.modifiedAt?.toIso8601String(), 'modifiedAt': instance.modifiedAt?.toIso8601String(),
'sort': instance.sort,
'fileId': instance.fileId, 'fileId': instance.fileId,
'hasPreview': instance.hasPreview, 'hasPreview': instance.hasPreview,
}; };
@@ -1,4 +1,3 @@
import 'package:jiffy/jiffy.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../../../../view/pages/files/data/sort_options.dart'; import '../../../../../view/pages/files/data/sort_options.dart';
@@ -22,60 +21,19 @@ class ListFilesResponse extends ApiResponse {
SortOption sortOption = SortOption.name, SortOption sortOption = SortOption.name,
bool reversed = false, bool reversed = false,
}) { }) {
var list = List<CacheableFile>.empty(growable: true); final ascending = SortOptions.getOption(sortOption).compare;
// `reversed=true` means user picked "Aufsteigend" (a→z, oldest→newest,
// smallest→largest); default is descending.
final compare = reversed
? ascending
: (CacheableFile a, CacheableFile b) => ascending(b, a);
if (foldersToTop) { if (!foldersToTop) {
list.addAll( return files.toList()..sort(compare);
_sort(
files.where((element) => element.isDirectory).toSet(),
reversed: reversed,
sortOption: sortOption,
),
);
list.addAll(
_sort(
files.where((element) => !element.isDirectory).toSet(),
reversed: reversed,
sortOption: sortOption,
),
);
} else {
list.addAll(_sort(files, reversed: reversed, sortOption: sortOption));
} }
return list; final folders = files.where((f) => f.isDirectory).toList()..sort(compare);
} final regular = files.where((f) => !f.isDirectory).toList()..sort(compare);
return [...folders, ...regular];
List<CacheableFile> _sort(
Set<CacheableFile> files, {
SortOption sortOption = SortOption.name,
bool reversed = false,
}) {
for (var file in files) {
final buffer = StringBuffer();
switch (sortOption) {
case SortOption.date:
buffer.write(
Jiffy.parseFromMillisecondsSinceEpoch(
file.modifiedAt?.millisecondsSinceEpoch ?? 0,
).format(pattern: 'yyyyMMddhhmmss'),
);
break;
case SortOption.name:
buffer.write(file.name.toLowerCase());
break;
case SortOption.size:
buffer.write(file.size);
break;
}
file.sort = buffer.toString();
}
var list = files.toList()..sort((a, b) => b.sort!.compareTo(a.sort!));
return reversed ? list.reversed.toList() : list;
} }
} }
+6 -2
View File
@@ -21,12 +21,16 @@ class SortOptions {
SortOption.name: BetterSortOption( SortOption.name: BetterSortOption(
displayName: 'Name', displayName: 'Name',
icon: Icons.sort_by_alpha_outlined, icon: Icons.sort_by_alpha_outlined,
compare: (a, b) => a.name.compareTo(b.name), compare: (a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()),
), ),
SortOption.date: BetterSortOption( SortOption.date: BetterSortOption(
displayName: 'Datum', displayName: 'Datum',
icon: Icons.history_outlined, icon: Icons.history_outlined,
compare: (a, b) => a.modifiedAt!.compareTo(b.modifiedAt!), compare: (a, b) {
final aMs = a.modifiedAt?.millisecondsSinceEpoch ?? 0;
final bMs = b.modifiedAt?.millisecondsSinceEpoch ?? 0;
return aMs.compareTo(bMs);
},
), ),
SortOption.size: BetterSortOption( SortOption.size: BetterSortOption(
displayName: 'Größe', displayName: 'Größe',
@@ -100,7 +100,6 @@ class ShareChatPicker extends StatelessWidget {
if (rooms == null) return const SizedBox.shrink(); if (rooms == null) return const SizedBox.shrink();
final sorted = rooms final sorted = rooms
.sortBy( .sortBy(
lastActivity: true,
favoritesToTop: talkSettings.sortFavoritesToTop, favoritesToTop: talkSettings.sortFavoritesToTop,
unreadToTop: talkSettings.sortUnreadToTop, unreadToTop: talkSettings.sortUnreadToTop,
) )
-1
View File
@@ -162,7 +162,6 @@ class _ChatListViewState extends State<_ChatListView> {
.val() .val()
.talkSettings; .talkSettings;
final sorted = rooms.sortBy( final sorted = rooms.sortBy(
lastActivity: true,
favoritesToTop: talkSettings.sortFavoritesToTop, favoritesToTop: talkSettings.sortFavoritesToTop,
unreadToTop: talkSettings.sortUnreadToTop, unreadToTop: talkSettings.sortUnreadToTop,
); );