Skip to content

Commit 1fe2ed4

Browse files
authored
fix: too eager caching of images (#314)
The fix is to compute the hashsum of the image data and send it to Flutter. On the other side, the image data is now always tagged with its hashsum in the image memory cache.
1 parent b859abe commit 1fe2ed4

File tree

13 files changed

+113
-50
lines changed

13 files changed

+113
-50
lines changed

Cargo.lock

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/lib/conversation_details/group_details.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ class GroupDetails extends StatelessWidget {
4040
size: 64,
4141
image: conversation.attributes.picture,
4242
username: conversation.username,
43-
cacheTag: conversation.avatarCacheTag,
4443
onPressed: () async {
4544
final conversationDetailsCubit =
4645
context.read<ConversationDetailsCubit>();

app/lib/conversation_list/conversation_list_content.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ class _ListTile extends StatelessWidget {
8888
children: [
8989
UserAvatar(
9090
size: 48,
91-
cacheTag: conversation.avatarCacheTag,
9291
image: conversation.attributes.picture,
9392
username: conversation.username,
9493
),

app/lib/core/core_extension.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
//
33
// SPDX-License-Identifier: AGPL-3.0-or-later
44

5+
import 'dart:typed_data';
6+
57
import 'package:prototype/core/core.dart';
68

79
extension UiConversationDetailsExtension on UiConversationDetails {
@@ -18,8 +20,6 @@ extension UiConversationDetailsExtension on UiConversationDetails {
1820
UiConversationType_Connection(field0: final e) => e,
1921
UiConversationType_Group() => attributes.title,
2022
};
21-
22-
String get avatarCacheTag => 'conv:$id:${attributes.picture?.hashCode}';
2323
}
2424

2525
extension UiConversationTypeExtension on UiConversationType {
@@ -43,3 +43,8 @@ extension UiFlightPositionExtension on UiFlightPosition {
4343
UiFlightPosition.single || UiFlightPosition.end => true,
4444
};
4545
}
46+
47+
extension ImageDataExtension on Uint8List {
48+
ImageData toImageData() =>
49+
ImageData(data: this, hash: ImageData.computeHash(this));
50+
}

app/lib/registration/display_name_picture.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'package:flutter/material.dart';
66
import 'package:image_picker/image_picker.dart';
7+
import 'package:prototype/core/core.dart';
78
import 'package:prototype/main.dart';
89
import 'package:prototype/navigation/navigation.dart';
910
import 'package:prototype/theme/theme.dart';
@@ -99,7 +100,7 @@ class _UserAvatarPicker extends StatelessWidget {
99100
final XFile? image =
100101
await picker.pickImage(source: ImageSource.gallery);
101102
final bytes = await image?.readAsBytes();
102-
registrationCubit.setAvatar(bytes);
103+
registrationCubit.setAvatar(bytes?.toImageData());
103104
},
104105
);
105106
}

app/lib/registration/registration_cubit.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
//
33
// SPDX-License-Identifier: AGPL-3.0-or-later
44

5-
import 'dart:typed_data';
6-
75
import 'package:flutter_bloc/flutter_bloc.dart';
86
import 'package:freezed_annotation/freezed_annotation.dart';
97
import 'package:logging/logging.dart';
@@ -36,7 +34,7 @@ sealed class RegistrationState with _$RegistrationState {
3634
@Default(false) bool isPasswordValid,
3735

3836
// Display name/avatar screen data
39-
Uint8List? avatar,
37+
ImageData? avatar,
4038
String? displayName,
4139
@Default(false) bool isSigningUp,
4240
}) = _RegistrationState;
@@ -74,7 +72,7 @@ class RegistrationCubit extends Cubit<RegistrationState> {
7472
));
7573
}
7674

77-
void setAvatar(Uint8List? bytes) {
75+
void setAvatar(ImageData? bytes) {
7876
emit(state.copyWith(avatar: bytes));
7977
}
8078

@@ -86,7 +84,9 @@ class RegistrationCubit extends Cubit<RegistrationState> {
8684
emit(state.copyWith(isSigningUp: true));
8785

8886
final fqun = "${state.username}@${state.domain}";
89-
final url = "https://${state.domain}";
87+
final url = state.domain == "localhost"
88+
? "http://${state.domain}"
89+
: "https://${state.domain}";
9090

9191
try {
9292
_log.info("Registering user ${state.username} ...");
@@ -95,7 +95,7 @@ class RegistrationCubit extends Cubit<RegistrationState> {
9595
state.password,
9696
url,
9797
state.displayName,
98-
state.avatar,
98+
state.avatar?.data,
9999
);
100100
} catch (e) {
101101
final message = "Error when registering user: ${e.toString()}";

app/lib/user/user_settings_screen.dart

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
//
33
// SPDX-License-Identifier: AGPL-3.0-or-later
44

5-
import 'dart:typed_data';
6-
75
import 'package:flutter/material.dart';
86
import 'package:image_picker/image_picker.dart';
7+
import 'package:prototype/core/core.dart';
98
import 'package:prototype/main.dart';
109
import 'package:prototype/theme/theme.dart';
1110
import 'package:prototype/user/user.dart';
@@ -21,7 +20,7 @@ class UserSettingsScreen extends StatefulWidget {
2120

2221
class _UserSettingsScreenState extends State<UserSettingsScreen> {
2322
String? newDisplayName;
24-
Uint8List? newProfilePicture;
23+
ImageData? newProfilePicture;
2524

2625
bool get _isChanged => newDisplayName != null || newProfilePicture != null;
2726

@@ -30,7 +29,9 @@ class _UserSettingsScreenState extends State<UserSettingsScreen> {
3029
final messenger = ScaffoldMessenger.of(context);
3130
try {
3231
await user.setProfile(
33-
displayName: newDisplayName, profilePicture: newProfilePicture);
32+
displayName: newDisplayName,
33+
profilePicture: newProfilePicture?.data,
34+
);
3435
setState(() {
3536
newDisplayName = null;
3637
newProfilePicture = null;
@@ -73,8 +74,9 @@ class _UserSettingsScreenState extends State<UserSettingsScreen> {
7374
final XFile? image =
7475
await picker.pickImage(source: ImageSource.gallery);
7576
final bytes = await image?.readAsBytes();
77+
final data = bytes?.toImageData();
7678
setState(() {
77-
newProfilePicture = bytes;
79+
newProfilePicture = data;
7880
});
7981
},
8082
),

app/lib/util/cached_memory_image.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:ui' as ui;
66

77
import 'package:flutter/foundation.dart';
88
import 'package:flutter/painting.dart';
9+
import 'package:prototype/core/core.dart';
910

1011
/// Same as [MemoryImage] but caches the result in memory under the given [tag]
1112
class CachedMemoryImage extends ImageProvider<CachedMemoryImage> {
@@ -14,6 +15,9 @@ class CachedMemoryImage extends ImageProvider<CachedMemoryImage> {
1415
this.bytes,
1516
);
1617

18+
factory CachedMemoryImage.fromImageData(ImageData imageData) =>
19+
CachedMemoryImage(imageData.hash, imageData.data);
20+
1721
final String tag;
1822
final Uint8List bytes;
1923

app/lib/widgets/user_avatar.dart

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,12 @@ class UserAvatar extends StatelessWidget {
1515
this.size = 24.0,
1616
this.image,
1717
this.onPressed,
18-
this.cacheTag,
1918
});
2019

2120
final String username;
2221
final double size;
23-
final Uint8List? image;
22+
final ImageData? image;
2423
final VoidCallback? onPressed;
25-
final String? cacheTag;
2624

2725
@override
2826
Widget build(BuildContext context) {
@@ -38,9 +36,8 @@ class UserAvatar extends StatelessWidget {
3836
child: CircleAvatar(
3937
radius: size / 2,
4038
backgroundColor: colorDMBLight,
41-
foregroundImage: (image != null)
42-
? CachedMemoryImage(cacheTag ?? "avatar:$username", image!)
43-
: null,
39+
foregroundImage:
40+
image != null ? CachedMemoryImage.fromImageData(image!) : null,
4441
child: Text(
4542
username.characters.firstOrNull?.toUpperCase() ?? "",
4643
style: TextStyle(
@@ -92,7 +89,6 @@ class _FutureUserAvatarState extends State<FutureUserAvatar> {
9289
image: snapshot.data?.profilePicture,
9390
size: widget.size,
9491
onPressed: widget.onPressed,
95-
cacheTag: widget.cacheTag,
9692
),
9793
);
9894
}

app/test/mocks.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ class MockUiUser implements UiUser {
2626
Uint8List? profilePicture,
2727
}) : _userName = userName,
2828
_displayName = displayName,
29-
_profilePicture = profilePicture;
29+
_profilePicture = profilePicture?.toImageData();
3030

3131
final String _userName;
3232
final String? _displayName;
33-
final Uint8List? _profilePicture;
33+
final ImageData? _profilePicture;
3434

3535
@override
3636
String? get displayName => _displayName;
@@ -42,7 +42,7 @@ class MockUiUser implements UiUser {
4242
bool get isDisposed => false;
4343

4444
@override
45-
Uint8List? get profilePicture => _profilePicture;
45+
ImageData? get profilePicture => _profilePicture;
4646

4747
@override
4848
String get userName => _userName;

applogic/Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@ crate-type = ["cdylib", "staticlib", "lib"]
2121
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(frb_expand)'] }
2222

2323
[dependencies]
24+
phnxcoreclient = { path = "../coreclient" }
25+
phnxapiclient = { path = "../apiclient" }
26+
phnxtypes = { path = "../types" }
27+
2428
tracing = "0.1"
2529
tracing-subscriber = { workspace = true }
2630
parking_lot = "0.12"
2731
uuid = { version = "1", features = ["v4"] }
28-
phnxcoreclient = { path = "../coreclient" }
29-
phnxapiclient = { path = "../apiclient" }
30-
phnxtypes = { path = "../types" }
3132
anyhow = { version = "1", features = ["backtrace"] }
3233
serde = { version = "1", features = ["derive"] }
3334
serde_json = "1"
@@ -36,7 +37,6 @@ flutter_rust_bridge = { version = "=2.7.0", features = ["chrono", "uuid"] }
3637
notify-rust = "4"
3738
chrono = { workspace = true }
3839
jni = "0.21"
39-
40-
# Workspace dependencies
4140
tokio-util = "0.7.13"
4241
tokio-stream = "0.1.17"
42+
blake3 = "1.5.5"

0 commit comments

Comments
 (0)