11import 'dart:async' ;
22import 'dart:io' show File;
33import 'package:flutter/material.dart' ;
4- import 'package:flutter/foundation.dart' show TargetPlatform;
54import 'package:flutter_animate/flutter_animate.dart' ;
65import 'package:desktop_drop/desktop_drop.dart' ;
76import 'package:provider/provider.dart' ;
7+ import 'package:re_editor/re_editor.dart' ;
88import '../../../l10n/app_localizations.dart' ;
99import '../../../main.dart' ;
1010import '../../../shared/widgets/interactive_drawer.dart' ;
@@ -66,7 +66,7 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
6666 final InteractiveDrawerController _drawerController = InteractiveDrawerController ();
6767 final ValueNotifier <int > _assistantPickerCloseTick = ValueNotifier <int >(0 );
6868 final FocusNode _inputFocus = FocusNode ();
69- final TextEditingController _inputController = TextEditingController ();
69+ final CodeLineEditingController _inputController = CodeLineEditingController ();
7070 final ChatInputBarController _mediaController = ChatInputBarController ();
7171 final ScrollController _scrollController = ScrollController ();
7272 final GlobalKey _inputBarKey = GlobalKey ();
@@ -177,20 +177,13 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
177177 if (! mounted) return ;
178178 final trimmed = text.trim ();
179179 if (trimmed.isEmpty) return ;
180- final current = _inputController.text;
181- final selection = _inputController.selection;
182- final start = (selection.start >= 0 && selection.start <= current.length)
183- ? selection.start
184- : current.length;
185- final end = (selection.end >= 0 && selection.end <= current.length && selection.end >= start)
186- ? selection.end
187- : start;
188- final next = current.replaceRange (start, end, trimmed);
189- _inputController.value = _inputController.value.copyWith (
190- text: next,
191- selection: TextSelection .collapsed (offset: start + trimmed.length),
192- composing: TextRange .empty,
193- );
180+ // Use CodeLineEditingController's replaceSelection to insert text at cursor
181+ try {
182+ _inputController.replaceSelection (trimmed);
183+ } catch (_) {
184+ // TODO: Add diagnostics (and/or a graceful fallback insert) when replaceSelection fails to avoid silent drops.
185+ return ;
186+ }
194187 WidgetsBinding .instance.addPostFrameCallback ((_) {
195188 if (! mounted) return ;
196189 _controller.forceScrollToBottomSoon (animate: false );
@@ -322,7 +315,7 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
322315 Widget w = content;
323316 if (! isAndroid) {
324317 w = w
325- .animate (key: ValueKey ('mob_body_' + ( _controller.currentConversation? .id ?? 'none' ) ))
318+ .animate (key: ValueKey ('mob_body_${ _controller .currentConversation ?.id ?? 'none' }' ))
326319 .fadeIn (duration: 200. ms, curve: Curves .easeOutCubic);
327320 w = FadeTransition (opacity: _controller.convoFade, child: w);
328321 }
@@ -468,7 +461,7 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
468461 context,
469462 dividerPadding: const EdgeInsets .symmetric (vertical: 8 , horizontal: 12 ),
470463 ),
471- ).animate (key: ValueKey ('tab_body_' + ( _controller.currentConversation? .id ?? 'none' ) ))
464+ ).animate (key: ValueKey ('tab_body_${ _controller .currentConversation ?.id ?? 'none' }' ))
472465 .fadeIn (duration: 200. ms, curve: Curves .easeOutCubic),
473466 ),
474467 ),
@@ -547,7 +540,7 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
547540 image: DecorationImage (
548541 image: provider,
549542 fit: BoxFit .cover,
550- colorFilter: ColorFilter .mode (Colors .black.withOpacity ( 0.04 ), BlendMode .srcATop),
543+ colorFilter: ColorFilter .mode (Colors .black.withValues (alpha : 0.04 ), BlendMode .srcATop),
551544 ),
552545 ),
553546 ),
@@ -563,8 +556,8 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
563556 final top = (0.20 * maskStrength).clamp (0.0 , 1.0 );
564557 final bottom = (0.50 * maskStrength).clamp (0.0 , 1.0 );
565558 return [
566- cs.background. withOpacity ( top),
567- cs.background. withOpacity ( bottom),
559+ cs.surface. withValues (alpha : top),
560+ cs.surface. withValues (alpha : bottom),
568561 ];
569562 }(),
570563 ),
@@ -601,16 +594,16 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
601594 child: Stack (
602595 fit: StackFit .expand,
603596 children: [
604- ColoredBox (color: cs.background ),
597+ ColoredBox (color: cs.surface ),
605598 if (bg != null ) Opacity (opacity: 0.9 , child: bg),
606599 DecoratedBox (
607600 decoration: BoxDecoration (
608601 gradient: LinearGradient (
609602 begin: Alignment .topCenter,
610603 end: Alignment .bottomCenter,
611604 colors: [
612- cs.background. withOpacity ( 0.08 ),
613- cs.background. withOpacity ( 0.36 ),
605+ cs.surface. withValues (alpha : 0.08 ),
606+ cs.surface. withValues (alpha : 0.36 ),
614607 ],
615608 ),
616609 ),
@@ -706,15 +699,20 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
706699 context.read <SettingsProvider >().setThinkingBudget (assistant.thinkingBudget);
707700 }
708701 await _openReasoningSettings ();
702+ if (! context.mounted) return ;
709703 final chosen = context.read <SettingsProvider >().thinkingBudget;
710704 await context.read <AssistantProvider >().updateAssistant (
711705 assistant.copyWith (thinkingBudget: chosen),
712706 );
713707 }
714708 },
715709 onSend: (text) {
710+ final trimmed = text.text.trim ();
711+ if (trimmed.isEmpty && text.imagePaths.isEmpty && text.documents.isEmpty) {
712+ return ;
713+ }
716714 _controller.sendMessage (text);
717- _inputController.clear ();
715+ _inputController.value = const CodeLineEditingValue . empty (); // Clear + reset selection/composing
718716 if (PlatformUtils .isMobile) {
719717 _controller.dismissKeyboard ();
720718 } else {
@@ -792,14 +790,14 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
792790 if (_controller.isDragHovering)
793791 IgnorePointer (
794792 child: Container (
795- color: Colors .black.withOpacity ( 0.12 ),
793+ color: Colors .black.withValues (alpha : 0.12 ),
796794 child: Center (
797795 child: Container (
798796 padding: const EdgeInsets .symmetric (horizontal: 24 , vertical: 16 ),
799797 decoration: BoxDecoration (
800- color: Theme .of (context).colorScheme.surface.withOpacity ( 0.95 ),
798+ color: Theme .of (context).colorScheme.surface.withValues (alpha : 0.95 ),
801799 borderRadius: BorderRadius .circular (12 ),
802- border: Border .all (color: Theme .of (context).colorScheme.primary.withOpacity ( 0.4 ), width: 2 ),
800+ border: Border .all (color: Theme .of (context).colorScheme.primary.withValues (alpha : 0.4 ), width: 2 ),
803801 ),
804802 child: Text (
805803 AppLocalizations .of (context)! .homePageDropToUpload,
@@ -839,6 +837,7 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
839837 final assistantId = context.read <AssistantProvider >().currentAssistantId;
840838 final provider = context.read <InstructionInjectionProvider >();
841839 await provider.initialize ();
840+ if (! mounted) return ;
842841 final items = provider.items;
843842 if (items.isEmpty) return ;
844843
0 commit comments