Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f55973c
Optimize chat and conversation paging performance
YeungKC Jun 17, 2026
cbc2a4f
Optimize message window and paging queries
YeungKC Jun 17, 2026
36159e8
Simplify chat UI state management
YeungKC Jun 18, 2026
5184cfb
Fix repeated conversation latest jump
YeungKC Jun 18, 2026
7fbd813
Remove remaining bloc state layer
YeungKC Jun 19, 2026
827a45f
Refactor desktop chat navigation and rendering
YeungKC Jun 22, 2026
f1fc2cf
Refactor chat navigation and render hot paths
YeungKC Jun 22, 2026
3febbd2
Optimize chat rendering and message window loading
YeungKC Jun 22, 2026
07c2834
Improve media image handling and avatar refresh
YeungKC Jun 22, 2026
31706db
Tighten chat viewport and image handling
YeungKC Jun 22, 2026
569fb00
Merge remote-tracking branch 'origin/main' into perf/chat-performance…
YeungKC Jun 22, 2026
14faf65
Refactor chat render responsibilities
YeungKC Jun 22, 2026
25ee6e1
Unify current conversation latest jump
YeungKC Jun 22, 2026
cf2ad4f
Fix chat media rendering edge cases
YeungKC Jun 22, 2026
7ec6f2e
Stabilize chat jump highlighting
YeungKC Jun 22, 2026
422df2d
Format image tests
YeungKC Jun 22, 2026
6f2d3f8
Improve chat scroll anchoring and settings navigation
YeungKC Jun 24, 2026
4009ed5
Keep unread anchor restore from jumping to latest
YeungKC Jun 24, 2026
6c27b89
Use native tail-follow scrolling for new chat messages
YeungKC Jun 24, 2026
6e7f39d
Fix double-tap quick reply
YeungKC Jun 24, 2026
5d9507c
Keep chat input attached to bottom
YeungKC Jun 24, 2026
2fb0d9d
Align unread chat anchor with message focus
YeungKC Jun 24, 2026
8c7a1a2
Scroll to latest after outgoing chat sends
YeungKC Jun 24, 2026
60244e1
Refactor chat UI state ownership
YeungKC Jun 24, 2026
8916d8f
Fix mention cache prewarm lifetime
YeungKC Jun 24, 2026
8dd27d8
Fix unread separator state without unread messages
YeungKC Jun 25, 2026
57b2fe7
Keep latest scroll pinned during inserts
YeungKC Jun 25, 2026
d97de2a
Fix quick reply double tap hit testing
YeungKC Jun 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/account/notification_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import '../db/extension/conversation.dart';
import '../enum/message_category.dart';
import '../generated/l10n.dart';

import '../ui/home/conversation/conversation_focus.dart';
import '../ui/provider/conversation_provider.dart';
import '../ui/provider/mention_cache_provider.dart';
import '../ui/provider/slide_category_provider.dart';
Expand Down Expand Up @@ -198,7 +199,7 @@ class NotificationService {

final conversationId =
event.queryParameters[_keyConversationId] ?? event.host;
ConversationStateNotifier.selectConversation(
ConversationFocus.selectConversation(
context,
conversationId,
initIndexMessageId: event.path,
Expand Down
22 changes: 14 additions & 8 deletions lib/account/send_message_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import '../utils/logger.dart';
import '../utils/reg_exp_utils.dart';
import '../widgets/message/item/action_card/action_card_data.dart';
import '../widgets/message/send_message_dialog/attachment_extra.dart';
import '../widgets/mixin_image.dart';
import 'show_pin_message_key_value.dart';

const jpegMimeType = 'image/jpeg';
Expand Down Expand Up @@ -1370,13 +1369,18 @@ class SendMessageHelper {
}

(Uint8List, ImageType, int, int)? result;
File? downloadedImage;
try {
final data = await downloadImage(url);
if (data != null) {
result = await compressWithIsolate(data);
downloadedImage = await downloadImageFile(url);
if (downloadedImage != null) {
result = await compressFileWithIsolate(downloadedImage);
}
} catch (error, stacktrace) {
e('failed to download image: $error $stacktrace');
} finally {
if (downloadedImage?.existsSync() == true) {
await downloadedImage!.delete();
}
}
if (result == null) {
e('failed to get send image bytes. $url');
Expand Down Expand Up @@ -1457,13 +1461,13 @@ class SendMessageHelper {
var mediaSize = await attachment.length();

if (attachment.lengthSync() == 0) {
Uint8List? sendImageBytes;
File? downloadedImage;
try {
sendImageBytes = await downloadImage(message.mediaUrl!);
downloadedImage = await downloadImageFile(message.mediaUrl!);
} catch (error, stacktrace) {
e('reUploadGiphyGif: failed to download image: $error $stacktrace');
}
if (sendImageBytes == null) {
if (downloadedImage == null) {
e(
'reUploadGiphyGif: failed to get send image bytes. ${message.mediaUrl}',
);
Expand All @@ -1473,7 +1477,9 @@ class SendMessageHelper {
);
return;
}
await attachment.writeAsBytes(sendImageBytes);
await downloadedImage.copy(attachment.path);
await downloadedImage.delete();
await normalizeGifFileIfNeeded(attachment, message.mediaMimeType);

thumbImage = await attachment.encodeBlurHash();
mediaSize = await attachment.length();
Expand Down
151 changes: 78 additions & 73 deletions lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,20 @@ import 'dart:io';
import 'package:drift/isolate.dart';
import 'package:drift/native.dart';
import 'package:flutter/material.dart' hide AnimatedTheme;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'
hide Consumer, FutureProvider, Provider;
hide ChangeNotifierProvider, Consumer, FutureProvider, Provider;
import 'package:provider/provider.dart';

import 'account/account_key_value.dart';
import 'account/notification_service.dart';
import 'constants/brightness_theme_data.dart';
import 'constants/resources.dart';
import 'generated/l10n.dart';
import 'ui/home/bloc/conversation_list_bloc.dart';
import 'ui/home/conversation/conversation_page.dart';
import 'ui/home/home.dart';
import 'ui/home/notifier/conversation_list_controller.dart';
import 'ui/landing/landing.dart';
import 'ui/landing/landing_failed.dart';
import 'ui/provider/account_server_provider.dart';
Expand All @@ -26,6 +25,7 @@ import 'ui/provider/mention_cache_provider.dart';
import 'ui/provider/multi_auth_provider.dart';
import 'ui/provider/setting_provider.dart';
import 'ui/provider/slide_category_provider.dart';
import 'utils/app_lifecycle.dart';
import 'utils/extension/extension.dart';
import 'utils/logger.dart';
import 'utils/platform.dart';
Expand Down Expand Up @@ -112,16 +112,12 @@ class _Providers extends HookConsumerWidget {
if (!asyncAccountServer.hasValue) return app;
final accountServer = asyncAccountServer.requireValue;

return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => ConversationListBloc(
ref.read(slideCategoryStateProvider.notifier),
accountServer.database,
ref.read(mentionCacheProvider),
),
),
],
return ChangeNotifierProvider<ConversationListController>(
create: (context) => ConversationListController(
ref.read(slideCategoryStateProvider.notifier),
accountServer.database,
ref.read(mentionCacheProvider),
),
child: Provider<NotificationService>(
create: (context) => NotificationService(context: context),
lazy: false,
Expand All @@ -138,66 +134,75 @@ class _App extends HookConsumerWidget {
final Widget home;

@override
Widget build(BuildContext context, WidgetRef ref) => WindowShortcuts(
child: GlobalMoveWindow(
child: MaterialApp(
title: 'Mixin',
navigatorObservers: [rootRouteObserver],
debugShowCheckedModeBanner: false,
localizationsDelegates: const [
Localization.delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [...Localization.delegate.supportedLocales],
theme: ThemeData(
colorScheme: ColorScheme.light(
primary: lightBrightnessThemeData.text,
),
textSelectionTheme: TextSelectionThemeData(
cursorColor: lightBrightnessThemeData.accent,
),
useMaterial3: true,
).withFallbackFonts(),
darkTheme: ThemeData(
colorScheme: ColorScheme.dark(
primary: darkBrightnessThemeData.text,
),
textSelectionTheme: TextSelectionThemeData(
cursorColor: darkBrightnessThemeData.accent,
),
useMaterial3: true,
).withFallbackFonts(),
themeMode: ref.watch(settingProvider).themeMode,
builder: (context, child) {
try {
context.accountServer.language = Localizations.localeOf(
context,
).languageCode;
} catch (_) {}
final mediaQueryData = MediaQuery.of(context);
return BrightnessObserver(
lightThemeData: lightBrightnessThemeData,
darkThemeData: darkBrightnessThemeData,
forceBrightness: ref.watch(settingProvider).brightness,
child: MediaQuery(
data: mediaQueryData.copyWith(
// Different linux distro change the value, e.g. 1.2
textScaler: Platform.isLinux
? TextScaler.noScaling
: mediaQueryData.textScaler,
Widget build(BuildContext context, WidgetRef ref) {
final appActive = useValueListenable(appActiveListener);

return TickerMode(
enabled: appActive,
child: WindowShortcuts(
child: GlobalMoveWindow(
child: MaterialApp(
title: 'Mixin',
navigatorObservers: [rootRouteObserver],
debugShowCheckedModeBanner: false,
localizationsDelegates: const [
Localization.delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [...Localization.delegate.supportedLocales],
theme: ThemeData(
colorScheme: ColorScheme.light(
primary: lightBrightnessThemeData.text,
),
textSelectionTheme: TextSelectionThemeData(
cursorColor: lightBrightnessThemeData.accent,
),
useMaterial3: true,
).withFallbackFonts(),
darkTheme: ThemeData(
colorScheme: ColorScheme.dark(
primary: darkBrightnessThemeData.text,
),
child: SystemTrayWidget(
child: TextInputActionHandler(child: AuthGuard(child: child!)),
textSelectionTheme: TextSelectionThemeData(
cursorColor: darkBrightnessThemeData.accent,
),
),
);
},
home: MixinAppActions(child: MacosMenuBar(child: home)),
useMaterial3: true,
).withFallbackFonts(),
themeMode: ref.watch(settingProvider).themeMode,
builder: (context, child) {
try {
context.accountServer.language = Localizations.localeOf(
context,
).languageCode;
} catch (_) {}
final mediaQueryData = MediaQuery.of(context);
return BrightnessObserver(
lightThemeData: lightBrightnessThemeData,
darkThemeData: darkBrightnessThemeData,
forceBrightness: ref.watch(settingProvider).brightness,
child: MediaQuery(
data: mediaQueryData.copyWith(
// Different linux distro change the value, e.g. 1.2
textScaler: Platform.isLinux
? TextScaler.noScaling
: mediaQueryData.textScaler,
),
child: SystemTrayWidget(
child: TextInputActionHandler(
child: AuthGuard(child: child!),
),
),
),
);
},
home: MixinAppActions(child: MacosMenuBar(child: home)),
),
),
),
),
);
);
}
}

class _Home extends HookConsumerWidget {
Expand Down Expand Up @@ -233,9 +238,9 @@ class _Home extends HookConsumerWidget {
}

if (deviceId.toLowerCase() != currentDeviceId.toLowerCase()) {
final multiAuthCubit = context.multiAuthChangeNotifier;
final multiAuthNotifier = context.multiAuthChangeNotifier;
await accountServer.signOutAndClear();
multiAuthCubit.signOut();
multiAuthNotifier.signOut();
}
} catch (e) {
w('checkDeviceId error: $e');
Expand All @@ -246,7 +251,7 @@ class _Home extends HookConsumerWidget {
}, [accountServer]);

if (accountServer != null) {
BlocProvider.of<ConversationListBloc>(context)
context.read<ConversationListController>()
..limit =
MediaQuery.sizeOf(context).height ~/
(ConversationPage.conversationItemHeight / 1.75)
Expand Down
106 changes: 0 additions & 106 deletions lib/bloc/bloc_converter.dart

This file was deleted.

Loading
Loading