1
1
import 'package:flutter/material.dart' ;
2
2
import 'package:flutter/services.dart' ;
3
3
import 'package:flutter_chatgpt_text_and_image_processing/configs/utils.dart' ;
4
- import 'package:rich_typewriter/rich_typewriter .dart' ;
4
+ import 'package:typewritertext/typewritertext .dart' ;
5
5
import '../../../models/chat_message.dart' ;
6
6
7
- class ChatMessageBubble extends StatelessWidget {
7
+ class ChatMessageBubble extends StatefulWidget {
8
8
final ChatMessage message;
9
9
final VoidCallback onShare;
10
+ final bool isNewMessage;
10
11
11
12
const ChatMessageBubble ({
12
13
super .key,
13
14
required this .message,
14
15
required this .onShare,
16
+ this .isNewMessage = false ,
15
17
});
16
18
19
+ @override
20
+ State <ChatMessageBubble > createState () => _ChatMessageBubbleState ();
21
+ }
22
+
23
+ class _ChatMessageBubbleState extends State <ChatMessageBubble > {
24
+ bool _showFullText = false ;
25
+
26
+ @override
27
+ void initState () {
28
+ super .initState ();
29
+ if (! widget.isNewMessage) {
30
+ _showFullText = true ;
31
+ }
32
+ }
33
+
17
34
@override
18
35
Widget build (BuildContext context) {
19
36
return Column (
20
37
crossAxisAlignment: CrossAxisAlignment .stretch,
21
38
children: [
22
39
_buildMessageBubble (
23
40
context,
24
- message.query,
41
+ widget. message.query,
25
42
isUser: true ,
26
43
),
27
44
const SizedBox (height: 8 ),
28
45
_buildMessageBubble (
29
46
context,
30
- message.response,
47
+ widget. message.response,
31
48
isUser: false ,
32
49
),
33
50
],
@@ -39,13 +56,23 @@ class ChatMessageBubble extends StatelessWidget {
39
56
final paragraphs = text.split ('\n\n ' );
40
57
41
58
for (final paragraph in paragraphs) {
42
- if (paragraph.startsWith ('**' ) && paragraph.endsWith ('**' )) {
43
- // Headers
59
+ if (paragraph.startsWith ('# ' )) {
60
+ // H1 Headers
61
+ spans.add (TextSpan (
62
+ text: '${paragraph .substring (2 )}\n ' ,
63
+ style: const TextStyle (
64
+ color: Colors .white,
65
+ fontSize: 24 ,
66
+ fontWeight: FontWeight .bold,
67
+ ),
68
+ ));
69
+ } else if (paragraph.startsWith ('## ' )) {
70
+ // H2 Headers
44
71
spans.add (TextSpan (
45
- text: '${paragraph .replaceAll ( '**' , '' )}\n ' ,
72
+ text: '${paragraph .substring ( 3 )}\n ' ,
46
73
style: const TextStyle (
47
74
color: Colors .white,
48
- fontSize: 18 ,
75
+ fontSize: 20 ,
49
76
fontWeight: FontWeight .bold,
50
77
),
51
78
));
@@ -103,14 +130,27 @@ class ChatMessageBubble extends StatelessWidget {
103
130
text,
104
131
style: const TextStyle (color: Colors .white),
105
132
)
133
+ else if (_showFullText)
134
+ SelectableText (
135
+ text,
136
+ style: const TextStyle (
137
+ color: Color (0xFFD1D5DB ),
138
+ fontSize: 16 ,
139
+ ),
140
+ )
106
141
else
107
- RichTypewriter (
108
- symbolDelay : (symbol) =>
109
- switch (symbol) { TextSpan (text : ' ' ) => 50 , TextSpan (text : ' \n ' ) => 200 , _ => 20 } ,
110
- onCompleted : () => debugPrint ( 'Finished typing response' ),
111
- child : SelectableText . rich (
112
- TextSpan (children : _processText (text)) ,
142
+ TypeWriter (
143
+ text : text,
144
+ duration : const Duration (milliseconds : 30 ) ,
145
+ textStyle : const TextStyle (
146
+ color : Color ( 0xFFD1D5DB ),
147
+ fontSize : 16 ,
113
148
),
149
+ onEnd: () {
150
+ setState (() {
151
+ _showFullText = true ;
152
+ });
153
+ },
114
154
),
115
155
if (! isUser) ...[
116
156
const Divider (color: Color (0xFF4D4D4D )),
@@ -134,7 +174,7 @@ class ChatMessageBubble extends StatelessWidget {
134
174
size: 20 ,
135
175
color: Color (0xFFD1D5DB ),
136
176
),
137
- onPressed: onShare,
177
+ onPressed: widget. onShare,
138
178
),
139
179
],
140
180
),
0 commit comments