Skip to content

Commit cfeb191

Browse files
Merge pull request #41 from ChandruWritesCode/v0.2.0
#40 added few features in the text box in chat page
2 parents f851b11 + aaf05ce commit cfeb191

5 files changed

Lines changed: 151 additions & 70 deletions

File tree

mobile/android/app/build.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ android {
1010
ndkVersion = flutter.ndkVersion
1111

1212
compileOptions {
13-
sourceCompatibility = JavaVersion.VERSION_25
14-
targetCompatibility = JavaVersion.VERSION_25
13+
sourceCompatibility = JavaVersion.VERSION_17
14+
targetCompatibility = JavaVersion.VERSION_17
1515
}
1616

1717
defaultConfig {
@@ -39,7 +39,7 @@ android {
3939

4040
kotlin {
4141
compilerOptions {
42-
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_25
42+
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
4343
}
4444
}
4545

mobile/lib/pages/chat_page.dart

Lines changed: 47 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -61,46 +61,57 @@ class _ChatPageState extends State<ChatPage> {
6161
name: widget.displayName,
6262
status: _getPresenceStatusText(chatState),
6363
),
64-
body: chatState.activeChat.isEmpty
65-
? const Center(
66-
child: Text(
67-
"No messages yet",
68-
style: TextStyle(color: Colors.grey, fontSize: 14),
69-
),
70-
)
71-
: ListView.builder(
72-
controller: _scrollController,
73-
padding: const EdgeInsets.only(
74-
top: 120,
75-
bottom: 100,
76-
left: 16,
77-
right: 16,
78-
),
79-
itemCount: chatState.activeChat.length,
80-
itemBuilder: (context, index) {
81-
final msg = chatState.activeChat[index];
64+
body: Stack(
65+
children: [
66+
chatState.activeChat.isEmpty
67+
? const Center(
68+
child: Text(
69+
"No messages yet",
70+
style: TextStyle(color: Colors.grey, fontSize: 14),
71+
),
72+
)
73+
: ListView.builder(
74+
controller: _scrollController,
75+
padding: const EdgeInsets.only(
76+
top: 120,
77+
bottom: 120,
78+
left: 16,
79+
right: 16,
80+
),
81+
itemCount: chatState.activeChat.length,
82+
itemBuilder: (context, index) {
83+
final msg = chatState.activeChat[index];
8284

83-
final String cleanSenderId = msg.senderId.trim().toLowerCase();
84-
final String cleanPeerId = widget.chatUserId
85-
.trim()
86-
.toLowerCase();
85+
final String cleanSenderId = msg.senderId
86+
.trim()
87+
.toLowerCase();
88+
final String cleanPeerId = widget.chatUserId
89+
.trim()
90+
.toLowerCase();
8791

88-
final bool isMe =
89-
cleanSenderId == 'me' ||
90-
(cleanSenderId.isNotEmpty && cleanSenderId != cleanPeerId);
92+
final bool isMe =
93+
cleanSenderId == 'me' ||
94+
(cleanSenderId.isNotEmpty &&
95+
cleanSenderId != cleanPeerId);
9196

92-
return ChatBubble(
93-
message: msg.content,
94-
isMe: isMe,
95-
timestamp: msg.createdAt,
96-
isRead: msg.isRead,
97-
);
98-
},
97+
return ChatBubble(
98+
message: msg.content,
99+
isMe: isMe,
100+
timestamp: msg.createdAt,
101+
isRead: msg.isRead,
102+
);
103+
},
104+
),
105+
Align(
106+
alignment: AlignmentGeometry.bottomCenter,
107+
child: ChatInputArea(
108+
onSendMessage: _sendMessage,
109+
onTypingChanged: (isTyping) => context
110+
.read<ChatController>()
111+
.sendTypingNotification(isTyping),
99112
),
100-
bottomNavigationBar: ChatInputArea(
101-
onSendMessage: _sendMessage,
102-
onTypingChanged: (isTyping) =>
103-
context.read<ChatController>().sendTypingNotification(isTyping),
113+
),
114+
],
104115
),
105116
);
106117
}

mobile/lib/pages/home_page.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,12 @@ class _HomePageState extends State<HomePage> {
204204
);
205205
}
206206

207+
final Set<String> _selectedChatIds = {};
208+
209+
bool get _isSelectionMode => _selectedChatIds.isNotEmpty;
210+
207211
Widget buildHomeTab(ChatController chatState) {
212+
208213
return RefreshIndicator(
209214
color: const Color(0xFF1890FF),
210215
onRefresh: () => chatState.loadInbox(),
@@ -317,7 +322,8 @@ class _HomePageState extends State<HomePage> {
317322
itemCount: chatState.inbox.length,
318323
itemBuilder: (context, index) {
319324
final thread = chatState.inbox[index];
320-
return CustomChatCard(conversation: thread);
325+
final bool isSelected = _selectedChatIds.contains(thread.chatUserId);
326+
return CustomChatCard(conversation: thread, isSelected: isSelected);
321327
},
322328
),
323329
SliverList(
@@ -327,7 +333,6 @@ class _HomePageState extends State<HomePage> {
327333
),
328334
);
329335
}
330-
331336
@override
332337
Widget build(BuildContext context) {
333338
final chatState = context.watch<ChatController>();

mobile/lib/widgets/chat_screen_modular_widgets.dart

Lines changed: 86 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,14 @@ class _ChatInputAreaState extends State<ChatInputArea> {
168168
super.dispose();
169169
}
170170

171+
final int _charLimit = 5000; // limit
172+
bool _isOverLimit = false;
173+
171174
void _handleOnChange(String text) {
175+
setState(() {
176+
_isOverLimit = text.length > _charLimit;
177+
});
178+
172179
final hasText = text.trim().isNotEmpty;
173180

174181
if (hasText && !_isTyping) {
@@ -197,6 +204,7 @@ class _ChatInputAreaState extends State<ChatInputArea> {
197204
}
198205

199206
void _submit() {
207+
_isOverLimit = false;
200208
final text = _controller.text.trim();
201209
if (text.isNotEmpty) {
202210
_typingTimer?.cancel();
@@ -214,37 +222,87 @@ class _ChatInputAreaState extends State<ChatInputArea> {
214222
@override
215223
Widget build(BuildContext context) {
216224
return SafeArea(
217-
child: Padding(
218-
padding: const EdgeInsets.all(12.0),
219-
child: Row(
220-
children: [
221-
Expanded(
222-
child: TextField(
223-
controller: _controller,
224-
onChanged: _handleOnChange,
225-
style: const TextStyle(color: Colors.black87, fontSize: 15),
226-
decoration: InputDecoration(
227-
hintText: "Write a message...",
228-
hintStyle: const TextStyle(color: Colors.black38),
229-
fillColor: const Color(0xFFF5F5F5),
230-
filled: true,
231-
border: OutlineInputBorder(
232-
borderRadius: BorderRadius.circular(24),
233-
borderSide: BorderSide.none,
234-
),
235-
contentPadding: const EdgeInsets.symmetric(
236-
horizontal: 16,
237-
vertical: 10,
225+
bottom: false,
226+
child: ClipRRect(
227+
child: BackdropFilter(
228+
filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15),
229+
child: Container(
230+
color: Colors.transparent,
231+
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 20),
232+
child: Row(
233+
mainAxisAlignment: MainAxisAlignment.center,
234+
crossAxisAlignment: CrossAxisAlignment.start,
235+
children: [
236+
Expanded(
237+
child: TextField(
238+
controller: _controller,
239+
onChanged: _handleOnChange,
240+
keyboardType: TextInputType.multiline,
241+
minLines: 1,
242+
maxLines: 5,
243+
244+
// 1. Set the limit
245+
maxLength: _charLimit,
246+
// 2. CRITICAL: This allows them to keep typing past the limit so it shows "7000/5000"
247+
maxLengthEnforcement: .none,
248+
249+
style: TextStyle(
250+
// 3. Turn the typed text red if they exceed the limit
251+
color: _isOverLimit ? Colors.red : Colors.black87,
252+
fontSize: 15,
253+
),
254+
decoration: InputDecoration(
255+
hintText: "Write a message...",
256+
hintStyle: const TextStyle(color: Colors.black38),
257+
258+
fillColor: _isOverLimit
259+
? Colors.red.withValues(alpha: 0.1)
260+
: const Color(0xFFF5F5F5),
261+
filled: true,
262+
errorText: _isOverLimit ? 'character limit exceeded' : '',
263+
counterStyle: TextStyle(
264+
color: _isOverLimit ? Colors.red : Colors.black54,
265+
fontWeight: _isOverLimit
266+
? FontWeight.bold
267+
: FontWeight.normal,
268+
),
269+
270+
border: OutlineInputBorder(
271+
borderRadius: BorderRadius.circular(24),
272+
borderSide: _isOverLimit
273+
? const BorderSide(color: Colors.red, width: 1.5)
274+
: BorderSide.none,
275+
),
276+
focusedBorder: OutlineInputBorder(
277+
borderRadius: BorderRadius.circular(24),
278+
borderSide: _isOverLimit
279+
? const BorderSide(color: Colors.red, width: 2)
280+
: BorderSide.none,
281+
),
282+
enabledBorder: OutlineInputBorder(
283+
borderRadius: BorderRadius.circular(24),
284+
borderSide: _isOverLimit
285+
? const BorderSide(color: Colors.red, width: 1.5)
286+
: BorderSide.none,
287+
),
288+
289+
contentPadding: const EdgeInsets.symmetric(
290+
horizontal: 16,
291+
vertical: 10,
292+
),
293+
),
238294
),
239295
),
240-
),
241-
),
242-
const SizedBox(width: 8),
243-
IconButton(
244-
icon: const Icon(Icons.send, color: Color(0xFF1890FF)),
245-
onPressed: _submit,
296+
const SizedBox(width: 8),
297+
IconButton(
298+
icon: _isOverLimit
299+
? const Icon(Icons.not_interested)
300+
: const Icon(Icons.send, color: Color(0xFF1890FF)),
301+
onPressed: _isOverLimit ? null : _submit,
302+
),
303+
],
246304
),
247-
],
305+
),
248306
),
249307
),
250308
);

mobile/lib/widgets/custom_cards.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,22 @@ import '../controllers/chat.dart';
66

77
class CustomChatCard extends StatelessWidget {
88
final Conversation conversation;
9+
final bool isSelected;
10+
final Function? onLongPress;
11+
final Function? onTap;
912

10-
const CustomChatCard({super.key, required this.conversation});
13+
const CustomChatCard({super.key, required this.conversation, required this.isSelected, this.onLongPress, this.onTap});
1114

1215
@override
1316
Widget build(BuildContext context) {
1417
final String timeLabel =
1518
"${conversation.lastMessageTime.hour.toString().padLeft(2, '0')}:${conversation.lastMessageTime.minute.toString().padLeft(2, '0')}";
1619

1720
return InkWell(
21+
onLongPress: () {
22+
23+
},
24+
1825
onTap: () async {
1926
final controller = context.read<ChatController>();
2027
await controller.openChat(conversation.chatUserId);

0 commit comments

Comments
 (0)