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' ;
@@ -69,7 +69,7 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
6969 final InteractiveDrawerController _drawerController = InteractiveDrawerController ();
7070 final ValueNotifier <int > _assistantPickerCloseTick = ValueNotifier <int >(0 );
7171 final FocusNode _inputFocus = FocusNode ();
72- final TextEditingController _inputController = TextEditingController ();
72+ final CodeLineEditingController _inputController = CodeLineEditingController ();
7373 final ChatInputBarController _mediaController = ChatInputBarController ();
7474 final ScrollController _scrollController = ScrollController ();
7575 final GlobalKey _inputBarKey = GlobalKey ();
@@ -184,20 +184,13 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
184184 if (! mounted) return ;
185185 final trimmed = text.trim ();
186186 if (trimmed.isEmpty) return ;
187- final current = _inputController.text;
188- final selection = _inputController.selection;
189- final start = (selection.start >= 0 && selection.start <= current.length)
190- ? selection.start
191- : current.length;
192- final end = (selection.end >= 0 && selection.end <= current.length && selection.end >= start)
193- ? selection.end
194- : start;
195- final next = current.replaceRange (start, end, trimmed);
196- _inputController.value = _inputController.value.copyWith (
197- text: next,
198- selection: TextSelection .collapsed (offset: start + trimmed.length),
199- composing: TextRange .empty,
200- );
187+ // Use CodeLineEditingController's replaceSelection to insert text at cursor
188+ try {
189+ _inputController.replaceSelection (trimmed);
190+ } catch (_) {
191+ // TODO: Add diagnostics (and/or a graceful fallback insert) when replaceSelection fails to avoid silent drops.
192+ return ;
193+ }
201194 WidgetsBinding .instance.addPostFrameCallback ((_) {
202195 if (! mounted) return ;
203196 _controller.forceScrollToBottomSoon (animate: false );
@@ -329,7 +322,7 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
329322 Widget w = content;
330323 if (! isAndroid) {
331324 w = w
332- .animate (key: ValueKey ('mob_body_' + ( _controller.currentConversation? .id ?? 'none' ) ))
325+ .animate (key: ValueKey ('mob_body_${ _controller .currentConversation ?.id ?? 'none' }' ))
333326 .fadeIn (duration: 200. ms, curve: Curves .easeOutCubic);
334327 w = FadeTransition (opacity: _controller.convoFade, child: w);
335328 }
@@ -475,7 +468,7 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
475468 context,
476469 dividerPadding: const EdgeInsets .symmetric (vertical: 8 , horizontal: 12 ),
477470 ),
478- ).animate (key: ValueKey ('tab_body_' + ( _controller.currentConversation? .id ?? 'none' ) ))
471+ ).animate (key: ValueKey ('tab_body_${ _controller .currentConversation ?.id ?? 'none' }' ))
479472 .fadeIn (duration: 200. ms, curve: Curves .easeOutCubic),
480473 ),
481474 ),
@@ -554,7 +547,7 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
554547 image: DecorationImage (
555548 image: provider,
556549 fit: BoxFit .cover,
557- colorFilter: ColorFilter .mode (Colors .black.withOpacity ( 0.04 ), BlendMode .srcATop),
550+ colorFilter: ColorFilter .mode (Colors .black.withValues (alpha : 0.04 ), BlendMode .srcATop),
558551 ),
559552 ),
560553 ),
@@ -570,8 +563,8 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
570563 final top = (0.20 * maskStrength).clamp (0.0 , 1.0 );
571564 final bottom = (0.50 * maskStrength).clamp (0.0 , 1.0 );
572565 return [
573- cs.background. withOpacity ( top),
574- cs.background. withOpacity ( bottom),
566+ cs.surface. withValues (alpha : top),
567+ cs.surface. withValues (alpha : bottom),
575568 ];
576569 }(),
577570 ),
@@ -608,16 +601,16 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
608601 child: Stack (
609602 fit: StackFit .expand,
610603 children: [
611- ColoredBox (color: cs.background ),
604+ ColoredBox (color: cs.surface ),
612605 if (bg != null ) Opacity (opacity: 0.9 , child: bg),
613606 DecoratedBox (
614607 decoration: BoxDecoration (
615608 gradient: LinearGradient (
616609 begin: Alignment .topCenter,
617610 end: Alignment .bottomCenter,
618611 colors: [
619- cs.background. withOpacity ( 0.08 ),
620- cs.background. withOpacity ( 0.36 ),
612+ cs.surface. withValues (alpha : 0.08 ),
613+ cs.surface. withValues (alpha : 0.36 ),
621614 ],
622615 ),
623616 ),
@@ -713,15 +706,20 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
713706 context.read <SettingsProvider >().setThinkingBudget (assistant.thinkingBudget);
714707 }
715708 await _openReasoningSettings ();
709+ if (! context.mounted) return ;
716710 final chosen = context.read <SettingsProvider >().thinkingBudget;
717711 await context.read <AssistantProvider >().updateAssistant (
718712 assistant.copyWith (thinkingBudget: chosen),
719713 );
720714 }
721715 },
722716 onSend: (text) {
717+ final trimmed = text.text.trim ();
718+ if (trimmed.isEmpty && text.imagePaths.isEmpty && text.documents.isEmpty) {
719+ return ;
720+ }
723721 _controller.sendMessage (text);
724- _inputController.clear ();
722+ _inputController.value = const CodeLineEditingValue . empty (); // Clear + reset selection/composing
725723 if (PlatformUtils .isMobile) {
726724 _controller.dismissKeyboard ();
727725 } else {
@@ -800,14 +798,14 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
800798 if (_controller.isDragHovering)
801799 IgnorePointer (
802800 child: Container (
803- color: Colors .black.withOpacity ( 0.12 ),
801+ color: Colors .black.withValues (alpha : 0.12 ),
804802 child: Center (
805803 child: Container (
806804 padding: const EdgeInsets .symmetric (horizontal: 24 , vertical: 16 ),
807805 decoration: BoxDecoration (
808- color: Theme .of (context).colorScheme.surface.withOpacity ( 0.95 ),
806+ color: Theme .of (context).colorScheme.surface.withValues (alpha : 0.95 ),
809807 borderRadius: BorderRadius .circular (12 ),
810- border: Border .all (color: Theme .of (context).colorScheme.primary.withOpacity ( 0.4 ), width: 2 ),
808+ border: Border .all (color: Theme .of (context).colorScheme.primary.withValues (alpha : 0.4 ), width: 2 ),
811809 ),
812810 child: Text (
813811 AppLocalizations .of (context)! .homePageDropToUpload,
@@ -847,6 +845,7 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
847845 final assistantId = context.read <AssistantProvider >().currentAssistantId;
848846 final provider = context.read <InstructionInjectionProvider >();
849847 await provider.initialize ();
848+ if (! mounted) return ;
850849 final items = provider.items;
851850 if (items.isEmpty) return ;
852851
0 commit comments