feat(chat): optimize performance and focus handling in chat UI
This commit is contained in:
@@ -1570,16 +1570,14 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
|||||||
height: 1.3,
|
height: 1.3,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Keyboard visibility
|
// Keyboard visibility - use viewInsetsOf for more efficient partial subscription
|
||||||
final keyboardVisible = MediaQuery.of(context).viewInsets.bottom > 0;
|
final keyboardVisible = MediaQuery.viewInsetsOf(context).bottom > 0;
|
||||||
// Whether the messages list can actually scroll (avoids showing button when not needed)
|
// Whether the messages list can actually scroll (avoids showing button when not needed)
|
||||||
final canScroll =
|
final canScroll =
|
||||||
_scrollController.hasClients &&
|
_scrollController.hasClients &&
|
||||||
_scrollController.position.maxScrollExtent > 0;
|
_scrollController.position.maxScrollExtent > 0;
|
||||||
// Check if any message is currently streaming (for scroll button indicator)
|
// Use dedicated streaming provider to avoid iterating all messages on rebuild
|
||||||
final isStreamingAnyMessage = ref
|
final isStreamingAnyMessage = ref.watch(isChatStreamingProvider);
|
||||||
.watch(chatMessagesProvider)
|
|
||||||
.any((msg) => msg.isStreaming);
|
|
||||||
|
|
||||||
// On keyboard open, if already near bottom, auto-scroll to bottom to keep input visible
|
// On keyboard open, if already near bottom, auto-scroll to bottom to keep input visible
|
||||||
if (keyboardVisible && !_lastKeyboardVisible) {
|
if (keyboardVisible && !_lastKeyboardVisible) {
|
||||||
@@ -1601,15 +1599,12 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focus composer on app startup once
|
// Focus composer on app startup once (minimal delay for layout to settle)
|
||||||
if (!_didStartupFocus) {
|
if (!_didStartupFocus) {
|
||||||
_didStartupFocus = true;
|
_didStartupFocus = true;
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
Future.delayed(const Duration(milliseconds: 200), () {
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final current = ref.read(inputFocusTriggerProvider);
|
ref.read(inputFocusTriggerProvider.notifier).increment();
|
||||||
ref.read(inputFocusTriggerProvider.notifier).set(current + 1);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import '../../../shared/theme/theme_extensions.dart';
|
import '../../../shared/theme/theme_extensions.dart';
|
||||||
// app_theme not required here; using theme extension tokens
|
// app_theme not required here; using theme extension tokens
|
||||||
@@ -183,6 +184,11 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
|||||||
}
|
}
|
||||||
|
|
||||||
_pendingFocus = true;
|
_pendingFocus = true;
|
||||||
|
// Request focus synchronously if we're already in a safe context,
|
||||||
|
// otherwise defer to next frame
|
||||||
|
if (WidgetsBinding.instance.schedulerPhase ==
|
||||||
|
SchedulerPhase.persistentCallbacks) {
|
||||||
|
// We're in a build/layout phase, defer to next frame
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_pendingFocus = false;
|
_pendingFocus = false;
|
||||||
@@ -190,6 +196,11 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
|||||||
_focusNode.requestFocus();
|
_focusNode.requestFocus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// Safe to request focus immediately
|
||||||
|
_pendingFocus = false;
|
||||||
|
_focusNode.requestFocus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -1032,11 +1043,8 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
final messages = ref.watch(chatMessagesProvider);
|
// Use dedicated streaming provider to avoid rebuilding on every message change
|
||||||
final isGenerating =
|
final isGenerating = ref.watch(isChatStreamingProvider);
|
||||||
messages.isNotEmpty &&
|
|
||||||
messages.last.role == 'assistant' &&
|
|
||||||
messages.last.isStreaming;
|
|
||||||
final stopGeneration = ref.read(stopGenerationProvider);
|
final stopGeneration = ref.read(stopGenerationProvider);
|
||||||
|
|
||||||
final webSearchEnabled = ref.watch(webSearchEnabledProvider);
|
final webSearchEnabled = ref.watch(webSearchEnabledProvider);
|
||||||
|
|||||||
Reference in New Issue
Block a user