improved performance and battery usage on older devices

This commit is contained in:
2026-09-25 23:58:08 +02:00
parent ed8e52f475
commit 56a497985b
70 changed files with 1631 additions and 621 deletions
@@ -17,7 +17,7 @@ class GetChatCache extends SimpleCache<GetChatResponse> {
lookIntoFuture: GetChatParamsSwitch.off,
setReadMarker: GetChatParamsSwitch.on,
// Small initial page; also the per-chat offline snapshot written to
// localstore. Older messages are paged in on scroll-up via
// the request cache. Older messages are paged in on scroll-up via
// GetChatHistory. Keep in sync with ChatBloc's _kInitialPageSize.
limit: 50,
),
@@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import '../../../errors/server_exception.dart';
@@ -45,8 +46,10 @@ class GetChatHistory {
final status = response.statusCode;
if (status == 304) return null;
if (status >= 200 && status < 300) {
return GetChatResponse.fromJson(NextcloudOcs.decode(response.body))
..headers = response.headers;
// A page holds up to 200 messages and lands while the user is scrolling;
// decoding it on the UI isolate stalls the fling.
final parsed = await compute(_parseChatResponse, response.body);
return parsed..headers = response.headers;
}
throw ServerException(
statusCode: status,
@@ -54,3 +57,6 @@ class GetChatHistory {
);
}
}
GetChatResponse _parseChatResponse(String body) =>
GetChatResponse.fromJson(NextcloudOcs.decode(body));
+10 -2
View File
@@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import '../../nextcloud_ocs.dart';
@@ -10,8 +11,12 @@ class GetRoom extends TalkApi<GetRoomResponse> {
GetRoom(this.params) : super('v4/room', null, getParameters: params.toJson());
@override
GetRoomResponse assemble(String raw) =>
GetRoomResponse.fromJson(NextcloudOcs.decode(raw));
GetRoomResponse assemble(String raw) => _parseRooms(raw);
// The room list with status is 100–250 KB and polled every 15–60 s.
@override
Future<GetRoomResponse> assembleAsync(String raw) =>
compute(_parseRooms, raw);
@override
Future<http.Response> request(
@@ -20,3 +25,6 @@ class GetRoom extends TalkApi<GetRoomResponse> {
Map<String, String>? headers,
) => http.get(uri, headers: headers);
}
GetRoomResponse _parseRooms(String raw) =>
GetRoomResponse.fromJson(NextcloudOcs.decode(raw));
+5 -1
View File
@@ -30,6 +30,10 @@ abstract class TalkApi<T extends ApiResponse?> {
);
T assemble(String raw);
/// Override to parse large payloads off the UI isolate (e.g. via `compute`
/// with a top-level parser); defaults to the synchronous [assemble].
Future<T> assembleAsync(String raw) async => assemble(raw);
Future<T> run() async {
final endpoint = NextcloudOcs.uri(
'apps/spreed/api/$path',
@@ -69,7 +73,7 @@ abstract class TalkApi<T extends ApiResponse?> {
}
try {
final assembled = assemble(data.body);
final assembled = await assembleAsync(data.body);
assembled?.headers = data.headers;
return assembled;
} catch (e) {
@@ -29,6 +29,19 @@ class CacheableFile {
/// file/folder in the list. Nullable so older cached entries decode fine.
bool? isSharedWithMe;
/// Lower-cased [name] for sorting/filtering, cached because comparators
/// call it for every comparison (name is mutable, hence the identity check).
String get lowerName {
if (!identical(_lowerNameFor, name)) {
_lowerNameFor = name;
_lowerName = name.toLowerCase();
}
return _lowerName;
}
String? _lowerNameFor;
String _lowerName = '';
CacheableFile({
required this.path,
required this.isDirectory,
@@ -1,9 +1,9 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:localstore/localstore.dart';
import '../../../../../utils/cache_invalidation_bus.dart';
import '../../../../cache_store.dart';
import '../../../../request_cache.dart';
import 'list_files.dart';
import 'list_files_params.dart';
@@ -43,14 +43,11 @@ class ListFilesCache extends SimpleCache<ListFilesResponse> {
/// (slow) root listing is already populated by the time the user
/// navigates to the Files module.
///
/// No-ops when a cached root payload is already present in localstore —
/// No-ops when a cached root payload is already present in the cache —
/// the regular TTL handling in [RequestCache] takes over from there.
static Future<void> prefetchRootListing() async {
const rootPath = '';
final cached = await Localstore.instance
.collection(RequestCache.collection)
.doc(_documentId(rootPath))
.get();
final cached = await CacheStore.instance.read(_documentId(rootPath));
if (cached != null) return;
// Drive the same code path as a regular fetch so the result lands in
// the cache; we don't care about the in-memory callback here.
@@ -69,10 +66,7 @@ class ListFilesCache extends SimpleCache<ListFilesResponse> {
/// `_FilesView` for that path via [CacheInvalidationBus] so it refetches
/// even while it is sitting in the background of the navigation stack.
static Future<void> invalidate(String path) async {
await Localstore.instance
.collection(RequestCache.collection)
.doc(_documentId(path))
.delete();
await CacheStore.instance.delete(_documentId(path));
CacheInvalidationBus.notifyListFiles(path);
}
}