From aaf05ceb61674f7913119dabc8b754ed7f15a4ed Mon Sep 17 00:00:00 2001 From: ChandruBtechCSE Date: Fri, 3 Jul 2026 11:07:22 +0530 Subject: [PATCH] added few features in the text box in chat page --- mobile/android/app/build.gradle.kts | 6 +- mobile/lib/pages/chat_page.dart | 83 +++++++------ mobile/lib/pages/home_page.dart | 9 +- .../widgets/chat_screen_modular_widgets.dart | 114 +++++++++++++----- mobile/lib/widgets/custom_cards.dart | 9 +- 5 files changed, 151 insertions(+), 70 deletions(-) diff --git a/mobile/android/app/build.gradle.kts b/mobile/android/app/build.gradle.kts index 87a0112..9d77a46 100644 --- a/mobile/android/app/build.gradle.kts +++ b/mobile/android/app/build.gradle.kts @@ -10,8 +10,8 @@ android { ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility = JavaVersion.VERSION_25 - targetCompatibility = JavaVersion.VERSION_25 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } defaultConfig { @@ -39,7 +39,7 @@ android { kotlin { compilerOptions { - jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_25 + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 } } diff --git a/mobile/lib/pages/chat_page.dart b/mobile/lib/pages/chat_page.dart index 789612f..76e01eb 100644 --- a/mobile/lib/pages/chat_page.dart +++ b/mobile/lib/pages/chat_page.dart @@ -61,46 +61,57 @@ class _ChatPageState extends State { name: widget.displayName, status: _getPresenceStatusText(chatState), ), - body: chatState.activeChat.isEmpty - ? const Center( - child: Text( - "No messages yet", - style: TextStyle(color: Colors.grey, fontSize: 14), - ), - ) - : ListView.builder( - controller: _scrollController, - padding: const EdgeInsets.only( - top: 120, - bottom: 100, - left: 16, - right: 16, - ), - itemCount: chatState.activeChat.length, - itemBuilder: (context, index) { - final msg = chatState.activeChat[index]; + body: Stack( + children: [ + chatState.activeChat.isEmpty + ? const Center( + child: Text( + "No messages yet", + style: TextStyle(color: Colors.grey, fontSize: 14), + ), + ) + : ListView.builder( + controller: _scrollController, + padding: const EdgeInsets.only( + top: 120, + bottom: 120, + left: 16, + right: 16, + ), + itemCount: chatState.activeChat.length, + itemBuilder: (context, index) { + final msg = chatState.activeChat[index]; - final String cleanSenderId = msg.senderId.trim().toLowerCase(); - final String cleanPeerId = widget.chatUserId - .trim() - .toLowerCase(); + final String cleanSenderId = msg.senderId + .trim() + .toLowerCase(); + final String cleanPeerId = widget.chatUserId + .trim() + .toLowerCase(); - final bool isMe = - cleanSenderId == 'me' || - (cleanSenderId.isNotEmpty && cleanSenderId != cleanPeerId); + final bool isMe = + cleanSenderId == 'me' || + (cleanSenderId.isNotEmpty && + cleanSenderId != cleanPeerId); - return ChatBubble( - message: msg.content, - isMe: isMe, - timestamp: msg.createdAt, - isRead: msg.isRead, - ); - }, + return ChatBubble( + message: msg.content, + isMe: isMe, + timestamp: msg.createdAt, + isRead: msg.isRead, + ); + }, + ), + Align( + alignment: AlignmentGeometry.bottomCenter, + child: ChatInputArea( + onSendMessage: _sendMessage, + onTypingChanged: (isTyping) => context + .read() + .sendTypingNotification(isTyping), ), - bottomNavigationBar: ChatInputArea( - onSendMessage: _sendMessage, - onTypingChanged: (isTyping) => - context.read().sendTypingNotification(isTyping), + ), + ], ), ); } diff --git a/mobile/lib/pages/home_page.dart b/mobile/lib/pages/home_page.dart index 1e4de5c..e751bb9 100644 --- a/mobile/lib/pages/home_page.dart +++ b/mobile/lib/pages/home_page.dart @@ -204,7 +204,12 @@ class _HomePageState extends State { ); } + final Set _selectedChatIds = {}; + + bool get _isSelectionMode => _selectedChatIds.isNotEmpty; + Widget buildHomeTab(ChatController chatState) { + return RefreshIndicator( color: const Color(0xFF1890FF), onRefresh: () => chatState.loadInbox(), @@ -317,7 +322,8 @@ class _HomePageState extends State { itemCount: chatState.inbox.length, itemBuilder: (context, index) { final thread = chatState.inbox[index]; - return CustomChatCard(conversation: thread); + final bool isSelected = _selectedChatIds.contains(thread.chatUserId); + return CustomChatCard(conversation: thread, isSelected: isSelected); }, ), SliverList( @@ -327,7 +333,6 @@ class _HomePageState extends State { ), ); } - @override Widget build(BuildContext context) { final chatState = context.watch(); diff --git a/mobile/lib/widgets/chat_screen_modular_widgets.dart b/mobile/lib/widgets/chat_screen_modular_widgets.dart index d89b376..368fc7d 100644 --- a/mobile/lib/widgets/chat_screen_modular_widgets.dart +++ b/mobile/lib/widgets/chat_screen_modular_widgets.dart @@ -168,7 +168,14 @@ class _ChatInputAreaState extends State { super.dispose(); } + final int _charLimit = 5000; // limit + bool _isOverLimit = false; + void _handleOnChange(String text) { + setState(() { + _isOverLimit = text.length > _charLimit; + }); + final hasText = text.trim().isNotEmpty; if (hasText && !_isTyping) { @@ -197,6 +204,7 @@ class _ChatInputAreaState extends State { } void _submit() { + _isOverLimit = false; final text = _controller.text.trim(); if (text.isNotEmpty) { _typingTimer?.cancel(); @@ -214,37 +222,87 @@ class _ChatInputAreaState extends State { @override Widget build(BuildContext context) { return SafeArea( - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Row( - children: [ - Expanded( - child: TextField( - controller: _controller, - onChanged: _handleOnChange, - style: const TextStyle(color: Colors.black87, fontSize: 15), - decoration: InputDecoration( - hintText: "Write a message...", - hintStyle: const TextStyle(color: Colors.black38), - fillColor: const Color(0xFFF5F5F5), - filled: true, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(24), - borderSide: BorderSide.none, - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 10, + bottom: false, + child: ClipRRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15), + child: Container( + color: Colors.transparent, + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: TextField( + controller: _controller, + onChanged: _handleOnChange, + keyboardType: TextInputType.multiline, + minLines: 1, + maxLines: 5, + + // 1. Set the limit + maxLength: _charLimit, + // 2. CRITICAL: This allows them to keep typing past the limit so it shows "7000/5000" + maxLengthEnforcement: .none, + + style: TextStyle( + // 3. Turn the typed text red if they exceed the limit + color: _isOverLimit ? Colors.red : Colors.black87, + fontSize: 15, + ), + decoration: InputDecoration( + hintText: "Write a message...", + hintStyle: const TextStyle(color: Colors.black38), + + fillColor: _isOverLimit + ? Colors.red.withValues(alpha: 0.1) + : const Color(0xFFF5F5F5), + filled: true, + errorText: _isOverLimit ? 'character limit exceeded' : '', + counterStyle: TextStyle( + color: _isOverLimit ? Colors.red : Colors.black54, + fontWeight: _isOverLimit + ? FontWeight.bold + : FontWeight.normal, + ), + + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(24), + borderSide: _isOverLimit + ? const BorderSide(color: Colors.red, width: 1.5) + : BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(24), + borderSide: _isOverLimit + ? const BorderSide(color: Colors.red, width: 2) + : BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(24), + borderSide: _isOverLimit + ? const BorderSide(color: Colors.red, width: 1.5) + : BorderSide.none, + ), + + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 10, + ), + ), ), ), - ), - ), - const SizedBox(width: 8), - IconButton( - icon: const Icon(Icons.send, color: Color(0xFF1890FF)), - onPressed: _submit, + const SizedBox(width: 8), + IconButton( + icon: _isOverLimit + ? const Icon(Icons.not_interested) + : const Icon(Icons.send, color: Color(0xFF1890FF)), + onPressed: _isOverLimit ? null : _submit, + ), + ], ), - ], + ), ), ), ); diff --git a/mobile/lib/widgets/custom_cards.dart b/mobile/lib/widgets/custom_cards.dart index 15598dd..3d4376f 100644 --- a/mobile/lib/widgets/custom_cards.dart +++ b/mobile/lib/widgets/custom_cards.dart @@ -6,8 +6,11 @@ import '../controllers/chat.dart'; class CustomChatCard extends StatelessWidget { final Conversation conversation; + final bool isSelected; + final Function? onLongPress; + final Function? onTap; - const CustomChatCard({super.key, required this.conversation}); + const CustomChatCard({super.key, required this.conversation, required this.isSelected, this.onLongPress, this.onTap}); @override Widget build(BuildContext context) { @@ -15,6 +18,10 @@ class CustomChatCard extends StatelessWidget { "${conversation.lastMessageTime.hour.toString().padLeft(2, '0')}:${conversation.lastMessageTime.minute.toString().padLeft(2, '0')}"; return InkWell( + onLongPress: () { + + }, + onTap: () async { final controller = context.read(); await controller.openChat(conversation.chatUserId);