Skip to content

Commit 885104e

Browse files
authored
Improve desktop chat rendering and navigation performance (#1995)
* Optimize chat and conversation paging performance * Optimize message window and paging queries * Simplify chat UI state management * Fix repeated conversation latest jump * Remove remaining bloc state layer * Refactor desktop chat navigation and rendering * Refactor chat navigation and render hot paths * Optimize chat rendering and message window loading * Improve media image handling and avatar refresh * Tighten chat viewport and image handling * Refactor chat render responsibilities * Unify current conversation latest jump * Fix chat media rendering edge cases * Stabilize chat jump highlighting * Format image tests * Improve chat scroll anchoring and settings navigation * Keep unread anchor restore from jumping to latest * Use native tail-follow scrolling for new chat messages * Fix double-tap quick reply * Keep chat input attached to bottom * Align unread chat anchor with message focus * Scroll to latest after outgoing chat sends * Refactor chat UI state ownership * Fix mention cache prewarm lifetime * Fix unread separator state without unread messages * Keep latest scroll pinned during inserts * Fix quick reply double tap hit testing
1 parent 08e77af commit 885104e

166 files changed

Lines changed: 11144 additions & 7232 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

lib/account/notification_service.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import '../db/extension/conversation.dart';
1010
import '../enum/message_category.dart';
1111
import '../generated/l10n.dart';
1212

13+
import '../ui/home/conversation/conversation_focus.dart';
1314
import '../ui/provider/conversation_provider.dart';
1415
import '../ui/provider/mention_cache_provider.dart';
1516
import '../ui/provider/slide_category_provider.dart';
@@ -198,7 +199,7 @@ class NotificationService {
198199

199200
final conversationId =
200201
event.queryParameters[_keyConversationId] ?? event.host;
201-
ConversationStateNotifier.selectConversation(
202+
ConversationFocus.selectConversation(
202203
context,
203204
conversationId,
204205
initIndexMessageId: event.path,

lib/account/send_message_helper.dart

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import '../utils/logger.dart';
3232
import '../utils/reg_exp_utils.dart';
3333
import '../widgets/message/item/action_card/action_card_data.dart';
3434
import '../widgets/message/send_message_dialog/attachment_extra.dart';
35-
import '../widgets/mixin_image.dart';
3635
import 'show_pin_message_key_value.dart';
3736

3837
const jpegMimeType = 'image/jpeg';
@@ -1370,13 +1369,18 @@ class SendMessageHelper {
13701369
}
13711370

13721371
(Uint8List, ImageType, int, int)? result;
1372+
File? downloadedImage;
13731373
try {
1374-
final data = await downloadImage(url);
1375-
if (data != null) {
1376-
result = await compressWithIsolate(data);
1374+
downloadedImage = await downloadImageFile(url);
1375+
if (downloadedImage != null) {
1376+
result = await compressFileWithIsolate(downloadedImage);
13771377
}
13781378
} catch (error, stacktrace) {
13791379
e('failed to download image: $error $stacktrace');
1380+
} finally {
1381+
if (downloadedImage?.existsSync() == true) {
1382+
await downloadedImage!.delete();
1383+
}
13801384
}
13811385
if (result == null) {
13821386
e('failed to get send image bytes. $url');
@@ -1457,13 +1461,13 @@ class SendMessageHelper {
14571461
var mediaSize = await attachment.length();
14581462

14591463
if (attachment.lengthSync() == 0) {
1460-
Uint8List? sendImageBytes;
1464+
File? downloadedImage;
14611465
try {
1462-
sendImageBytes = await downloadImage(message.mediaUrl!);
1466+
downloadedImage = await downloadImageFile(message.mediaUrl!);
14631467
} catch (error, stacktrace) {
14641468
e('reUploadGiphyGif: failed to download image: $error $stacktrace');
14651469
}
1466-
if (sendImageBytes == null) {
1470+
if (downloadedImage == null) {
14671471
e(
14681472
'reUploadGiphyGif: failed to get send image bytes. ${message.mediaUrl}',
14691473
);
@@ -1473,7 +1477,9 @@ class SendMessageHelper {
14731477
);
14741478
return;
14751479
}
1476-
await attachment.writeAsBytes(sendImageBytes);
1480+
await downloadedImage.copy(attachment.path);
1481+
await downloadedImage.delete();
1482+
await normalizeGifFileIfNeeded(attachment, message.mediaMimeType);
14771483

14781484
thumbImage = await attachment.encodeBlurHash();
14791485
mediaSize = await attachment.length();

lib/app.dart

Lines changed: 78 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,20 @@ import 'dart:io';
33
import 'package:drift/isolate.dart';
44
import 'package:drift/native.dart';
55
import 'package:flutter/material.dart' hide AnimatedTheme;
6-
import 'package:flutter_bloc/flutter_bloc.dart';
76
import 'package:flutter_hooks/flutter_hooks.dart';
87
import 'package:flutter_localizations/flutter_localizations.dart';
98
import 'package:hooks_riverpod/hooks_riverpod.dart'
10-
hide Consumer, FutureProvider, Provider;
9+
hide ChangeNotifierProvider, Consumer, FutureProvider, Provider;
1110
import 'package:provider/provider.dart';
1211

1312
import 'account/account_key_value.dart';
1413
import 'account/notification_service.dart';
1514
import 'constants/brightness_theme_data.dart';
1615
import 'constants/resources.dart';
1716
import 'generated/l10n.dart';
18-
import 'ui/home/bloc/conversation_list_bloc.dart';
1917
import 'ui/home/conversation/conversation_page.dart';
2018
import 'ui/home/home.dart';
19+
import 'ui/home/notifier/conversation_list_controller.dart';
2120
import 'ui/landing/landing.dart';
2221
import 'ui/landing/landing_failed.dart';
2322
import 'ui/provider/account_server_provider.dart';
@@ -26,6 +25,7 @@ import 'ui/provider/mention_cache_provider.dart';
2625
import 'ui/provider/multi_auth_provider.dart';
2726
import 'ui/provider/setting_provider.dart';
2827
import 'ui/provider/slide_category_provider.dart';
28+
import 'utils/app_lifecycle.dart';
2929
import 'utils/extension/extension.dart';
3030
import 'utils/logger.dart';
3131
import 'utils/platform.dart';
@@ -112,16 +112,12 @@ class _Providers extends HookConsumerWidget {
112112
if (!asyncAccountServer.hasValue) return app;
113113
final accountServer = asyncAccountServer.requireValue;
114114

115-
return MultiBlocProvider(
116-
providers: [
117-
BlocProvider(
118-
create: (context) => ConversationListBloc(
119-
ref.read(slideCategoryStateProvider.notifier),
120-
accountServer.database,
121-
ref.read(mentionCacheProvider),
122-
),
123-
),
124-
],
115+
return ChangeNotifierProvider<ConversationListController>(
116+
create: (context) => ConversationListController(
117+
ref.read(slideCategoryStateProvider.notifier),
118+
accountServer.database,
119+
ref.read(mentionCacheProvider),
120+
),
125121
child: Provider<NotificationService>(
126122
create: (context) => NotificationService(context: context),
127123
lazy: false,
@@ -138,66 +134,75 @@ class _App extends HookConsumerWidget {
138134
final Widget home;
139135

140136
@override
141-
Widget build(BuildContext context, WidgetRef ref) => WindowShortcuts(
142-
child: GlobalMoveWindow(
143-
child: MaterialApp(
144-
title: 'Mixin',
145-
navigatorObservers: [rootRouteObserver],
146-
debugShowCheckedModeBanner: false,
147-
localizationsDelegates: const [
148-
Localization.delegate,
149-
GlobalMaterialLocalizations.delegate,
150-
GlobalCupertinoLocalizations.delegate,
151-
GlobalWidgetsLocalizations.delegate,
152-
],
153-
supportedLocales: [...Localization.delegate.supportedLocales],
154-
theme: ThemeData(
155-
colorScheme: ColorScheme.light(
156-
primary: lightBrightnessThemeData.text,
157-
),
158-
textSelectionTheme: TextSelectionThemeData(
159-
cursorColor: lightBrightnessThemeData.accent,
160-
),
161-
useMaterial3: true,
162-
).withFallbackFonts(),
163-
darkTheme: ThemeData(
164-
colorScheme: ColorScheme.dark(
165-
primary: darkBrightnessThemeData.text,
166-
),
167-
textSelectionTheme: TextSelectionThemeData(
168-
cursorColor: darkBrightnessThemeData.accent,
169-
),
170-
useMaterial3: true,
171-
).withFallbackFonts(),
172-
themeMode: ref.watch(settingProvider).themeMode,
173-
builder: (context, child) {
174-
try {
175-
context.accountServer.language = Localizations.localeOf(
176-
context,
177-
).languageCode;
178-
} catch (_) {}
179-
final mediaQueryData = MediaQuery.of(context);
180-
return BrightnessObserver(
181-
lightThemeData: lightBrightnessThemeData,
182-
darkThemeData: darkBrightnessThemeData,
183-
forceBrightness: ref.watch(settingProvider).brightness,
184-
child: MediaQuery(
185-
data: mediaQueryData.copyWith(
186-
// Different linux distro change the value, e.g. 1.2
187-
textScaler: Platform.isLinux
188-
? TextScaler.noScaling
189-
: mediaQueryData.textScaler,
137+
Widget build(BuildContext context, WidgetRef ref) {
138+
final appActive = useValueListenable(appActiveListener);
139+
140+
return TickerMode(
141+
enabled: appActive,
142+
child: WindowShortcuts(
143+
child: GlobalMoveWindow(
144+
child: MaterialApp(
145+
title: 'Mixin',
146+
navigatorObservers: [rootRouteObserver],
147+
debugShowCheckedModeBanner: false,
148+
localizationsDelegates: const [
149+
Localization.delegate,
150+
GlobalMaterialLocalizations.delegate,
151+
GlobalCupertinoLocalizations.delegate,
152+
GlobalWidgetsLocalizations.delegate,
153+
],
154+
supportedLocales: [...Localization.delegate.supportedLocales],
155+
theme: ThemeData(
156+
colorScheme: ColorScheme.light(
157+
primary: lightBrightnessThemeData.text,
158+
),
159+
textSelectionTheme: TextSelectionThemeData(
160+
cursorColor: lightBrightnessThemeData.accent,
161+
),
162+
useMaterial3: true,
163+
).withFallbackFonts(),
164+
darkTheme: ThemeData(
165+
colorScheme: ColorScheme.dark(
166+
primary: darkBrightnessThemeData.text,
190167
),
191-
child: SystemTrayWidget(
192-
child: TextInputActionHandler(child: AuthGuard(child: child!)),
168+
textSelectionTheme: TextSelectionThemeData(
169+
cursorColor: darkBrightnessThemeData.accent,
193170
),
194-
),
195-
);
196-
},
197-
home: MixinAppActions(child: MacosMenuBar(child: home)),
171+
useMaterial3: true,
172+
).withFallbackFonts(),
173+
themeMode: ref.watch(settingProvider).themeMode,
174+
builder: (context, child) {
175+
try {
176+
context.accountServer.language = Localizations.localeOf(
177+
context,
178+
).languageCode;
179+
} catch (_) {}
180+
final mediaQueryData = MediaQuery.of(context);
181+
return BrightnessObserver(
182+
lightThemeData: lightBrightnessThemeData,
183+
darkThemeData: darkBrightnessThemeData,
184+
forceBrightness: ref.watch(settingProvider).brightness,
185+
child: MediaQuery(
186+
data: mediaQueryData.copyWith(
187+
// Different linux distro change the value, e.g. 1.2
188+
textScaler: Platform.isLinux
189+
? TextScaler.noScaling
190+
: mediaQueryData.textScaler,
191+
),
192+
child: SystemTrayWidget(
193+
child: TextInputActionHandler(
194+
child: AuthGuard(child: child!),
195+
),
196+
),
197+
),
198+
);
199+
},
200+
home: MixinAppActions(child: MacosMenuBar(child: home)),
201+
),
202+
),
198203
),
199-
),
200-
);
204+
);
205+
}
201206
}
202207

203208
class _Home extends HookConsumerWidget {
@@ -233,9 +238,9 @@ class _Home extends HookConsumerWidget {
233238
}
234239

235240
if (deviceId.toLowerCase() != currentDeviceId.toLowerCase()) {
236-
final multiAuthCubit = context.multiAuthChangeNotifier;
241+
final multiAuthNotifier = context.multiAuthChangeNotifier;
237242
await accountServer.signOutAndClear();
238-
multiAuthCubit.signOut();
243+
multiAuthNotifier.signOut();
239244
}
240245
} catch (e) {
241246
w('checkDeviceId error: $e');
@@ -246,7 +251,7 @@ class _Home extends HookConsumerWidget {
246251
}, [accountServer]);
247252

248253
if (accountServer != null) {
249-
BlocProvider.of<ConversationListBloc>(context)
254+
context.read<ConversationListController>()
250255
..limit =
251256
MediaQuery.sizeOf(context).height ~/
252257
(ConversationPage.conversationItemHeight / 1.75)

lib/bloc/bloc_converter.dart

Lines changed: 0 additions & 106 deletions
This file was deleted.

0 commit comments

Comments
 (0)