From 1ecf714c77e6f3adf82e9e7bab9a9d78f1190e74 Mon Sep 17 00:00:00 2001 From: ChandruBtechCSE Date: Sun, 5 Jul 2026 19:52:16 +0530 Subject: [PATCH 1/2] fixed markdown issue on chatcard in homepage --- mobile/lib/pages/settings pages/accounts.dart | 1 - mobile/lib/widgets/custom_cards.dart | 92 +++++++++++++------ mobile/pubspec.lock | 72 +++++++++++++++ mobile/pubspec.yaml | 1 + 4 files changed, 135 insertions(+), 31 deletions(-) diff --git a/mobile/lib/pages/settings pages/accounts.dart b/mobile/lib/pages/settings pages/accounts.dart index 416a23b..7be8a3c 100644 --- a/mobile/lib/pages/settings pages/accounts.dart +++ b/mobile/lib/pages/settings pages/accounts.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:mobile/controllers/auth.dart'; -import 'package:mobile/controllers/chat.dart'; import 'package:mobile/providers/basic_providers.dart'; import 'package:mobile/main.dart'; import 'package:provider/provider.dart'; diff --git a/mobile/lib/widgets/custom_cards.dart b/mobile/lib/widgets/custom_cards.dart index dc2f870..3b98536 100644 --- a/mobile/lib/widgets/custom_cards.dart +++ b/mobile/lib/widgets/custom_cards.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_markdown_plus/flutter_markdown_plus.dart'; import 'package:provider/provider.dart'; +import 'package:simple_rich_text/simple_rich_text.dart'; import '../models/conversation.dart'; import '../pages/chat_page.dart'; import '../controllers/chat.dart'; @@ -119,36 +120,67 @@ class CustomChatCard extends StatelessWidget { ), ), const SizedBox(height: 4), - MarkdownBody( - data: conversation.lastMessage, - selectable: false, - styleSheet: MarkdownStyleSheet( - p: TextStyle( - color: hasUnread ? Colors.black87 : Colors.black54, - fontSize: 15, - ), - strong: TextStyle( - color: hasUnread - ? Colors.black87 - : const Color.fromARGB(171, 0, 0, 0), - fontWeight: FontWeight.bold, - ), - em: TextStyle( - color: hasUnread ? Colors.black87 : Colors.black54, - fontStyle: FontStyle.italic, - ), - del: TextStyle( - color: hasUnread ? Colors.black87 : Colors.black54, - decoration: TextDecoration.lineThrough, - ), - code: TextStyle( - color: hasUnread ? Colors.black87 : Colors.black54, - fontFamily: 'monospace', - ), - a: TextStyle( - color: hasUnread ? Colors.black87 : Colors.black54, - decoration: TextDecoration.underline, - ), + // SizedBox( + // height: 25, + // width: double.infinity, + // child: ClipRect( + // child: MarkdownBody( + // data: conversation.lastMessage, + // selectable: false, + // shrinkWrap: true, + // softLineBreak: true, + // styleSheet: MarkdownStyleSheet( + // p: TextStyle( + // color: hasUnread + // ? Colors.black87 + // : Colors.black54, + // fontSize: 15, + // ), + // strong: TextStyle( + // color: hasUnread + // ? Colors.black87 + // : const Color.fromARGB(171, 0, 0, 0), + // fontWeight: FontWeight.bold, + // ), + // em: TextStyle( + // color: hasUnread + // ? Colors.black87 + // : Colors.black54, + // fontStyle: FontStyle.italic, + // ), + // del: TextStyle( + // color: hasUnread + // ? Colors.black87 + // : Colors.black54, + // decoration: TextDecoration.lineThrough, + // ), + // code: TextStyle( + // color: hasUnread + // ? Colors.black87 + // : Colors.black54, + // fontFamily: 'monospace', + // ), + // a: TextStyle( + // color: hasUnread + // ? Colors.black87 + // : Colors.black54, + // decoration: TextDecoration.underline, + // ), + // ), + // ), + // ), + // ), + // Example using simple_rich_text + SimpleRichText( + conversation.lastMessage.replaceAll( + '\n', + ' ', + ), + maxLines: 1, + textOverflow: TextOverflow.ellipsis, + style: TextStyle( + color: hasUnread ? Colors.black87 : Colors.black54, + fontSize: 15, ), ), // Text( diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index e6dd526..7d963b8 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -800,6 +800,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.1" + simple_rich_text: + dependency: "direct main" + description: + name: simple_rich_text + sha256: ba678f8075336995604ccc1e8bf407dc6455e0d6dee17c2aabadba38a9dbd23f + url: "https://pub.dev" + source: hosted + version: "2.0.49" sky_engine: dependency: transitive description: flutter @@ -909,6 +917,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32 + url: "https://pub.dev" + source: hosted + version: "6.3.32" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" vector_math: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index d637663..cd24f72 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -50,6 +50,7 @@ dependencies: dio: ^5.10.0 flutter_markdown_plus: ^1.0.7 intl: ^0.20.3 + simple_rich_text: ^2.0.49 launcher_name: default: "Elephant" From ba13c7f15d5678d12f131587fbdfd989e2a957d6 Mon Sep 17 00:00:00 2001 From: ChandruBtechCSE Date: Sun, 5 Jul 2026 21:17:36 +0530 Subject: [PATCH 2/2] integrated api calls to create groups --- mobile/lib/controllers/chat.dart | 10 +- mobile/lib/main.dart | 2 + mobile/lib/pages/select_contact_page.dart | 135 +++++++++++++++++- .../providers/group_controller_provider.dart | 79 ++++++++++ mobile/lib/services/api.dart | 8 ++ mobile/lib/widgets/custom_cards.dart | 1 - 6 files changed, 230 insertions(+), 5 deletions(-) create mode 100644 mobile/lib/providers/group_controller_provider.dart diff --git a/mobile/lib/controllers/chat.dart b/mobile/lib/controllers/chat.dart index 812a9be..4c58cdc 100644 --- a/mobile/lib/controllers/chat.dart +++ b/mobile/lib/controllers/chat.dart @@ -19,8 +19,12 @@ class ChatController extends ChangeNotifier { bool isPeerTyping = false; bool isPeerOnline = false; bool isSearchLoading = false; + bool _isWsInitialized = false; Future initSession(String token) async { + if (_isWsInitialized) return; + _isWsInitialized = true; + await _ws.connect(token); _ws.stream?.listen( (rawFrame) { @@ -34,8 +38,12 @@ class ChatController extends ChangeNotifier { } }, onError: (err) => debugPrint("WS Pipeline Error: $err"), - onDone: () => _ws.disconnect(), + onDone: () { + _ws.disconnect(); + _isWsInitialized = false; + }, ); + await loadInbox(); } diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 6d676ea..a1be3d8 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:mobile/providers/basic_providers.dart'; +import 'package:mobile/providers/group_controller_provider.dart'; import 'package:provider/provider.dart'; import 'core/constants.dart'; import 'controllers/auth.dart'; @@ -16,6 +17,7 @@ void main() async { ChangeNotifierProvider(create: (_) => BasicProviders()), ChangeNotifierProvider(create: (_) => AuthState()), ChangeNotifierProvider(create: (_) => ChatController()), + ChangeNotifierProvider(create: (_) => GroupController()), ], child: const MyApp(), ), diff --git a/mobile/lib/pages/select_contact_page.dart b/mobile/lib/pages/select_contact_page.dart index 113582d..4d4a474 100644 --- a/mobile/lib/pages/select_contact_page.dart +++ b/mobile/lib/pages/select_contact_page.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:mobile/controllers/chat.dart'; +import 'package:mobile/pages/chat_page.dart'; +import 'package:mobile/providers/group_controller_provider.dart'; import 'package:provider/provider.dart'; class SelectContactPage extends StatefulWidget { @@ -15,7 +17,9 @@ class _SelectContactPageState extends State { Timer? _debounceTimer; bool _isSearchOpen = false; + final _searchController = TextEditingController(); + final _groupNameController = TextEditingController(); void _onSearchChanged(String value, ChatController chatState) { if (_debounceTimer?.isActive ?? false) _debounceTimer!.cancel(); @@ -42,6 +46,7 @@ class _SelectContactPageState extends State { void dispose() { _debounceTimer?.cancel(); _searchController.dispose(); + _groupNameController.dispose(); super.dispose(); } @@ -270,7 +275,6 @@ class _SelectContactPageState extends State { ), ), ], - const Padding( padding: EdgeInsets.only(left: 16, top: 20, bottom: 8), child: Text( @@ -282,7 +286,6 @@ class _SelectContactPageState extends State { ), ), ), - if (chatState.inbox.isNotEmpty) ...chatState.inbox .where( @@ -304,10 +307,136 @@ class _SelectContactPageState extends State { ), floatingActionButton: _selectedContacts.isNotEmpty ? FloatingActionButton( - onPressed: () {}, backgroundColor: Colors.blue, elevation: 4, child: const Icon(Icons.arrow_forward, color: Colors.white), + onPressed: () { + context.read().addContacts(_selectedContacts); + + showModalBottomSheet( + context: context, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(16), + ), + ), + builder: (BuildContext bottomSheetContext) { + return Consumer( + builder: (context, groupState, child) { + return Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of( + bottomSheetContext, + ).viewInsets.bottom, + left: 16, + right: 16, + top: 24, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'New Group', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + TextField( + controller: _groupNameController, + autofocus: true, + decoration: const InputDecoration( + hintText: 'Enter group name', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FilledButton( + onPressed: groupState.isLoading + ? null + : () async { + final groupName = + _groupNameController.text + .trim(); + if (groupName.isEmpty) return; + + final memberIds = _selectedContacts + .keys + .toList(); + + final newGroup = await context + .read() + .createGroup( + groupName: groupName, + memberIds: memberIds, + ); + + if (newGroup != null) { + if (!context.mounted) return; + + context + .read() + .loadInbox(); + + Navigator.of( + bottomSheetContext, + ).pop(); + Navigator.of( + context, + ).pushReplacement( + MaterialPageRoute( + builder: (context) => ChatPage( + chatUserId: newGroup + .id, + displayName: newGroup + .name, + ), + ), + ); + + context + .read() + .openChat(newGroup.id); + } else { + if (!context.mounted) return; + ScaffoldMessenger.of( + context, + ).showSnackBar( + const SnackBar( + content: Text( + 'Failed to create group', + ), + ), + ); + } + }, + child: groupState.isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ) + : const Text('Create'), + ), + ], + ), + const SizedBox(height: 16), + ], + ), + ); + }, + ); + }, + ); + }, ) : null, ); diff --git a/mobile/lib/providers/group_controller_provider.dart b/mobile/lib/providers/group_controller_provider.dart new file mode 100644 index 0000000..8f68448 --- /dev/null +++ b/mobile/lib/providers/group_controller_provider.dart @@ -0,0 +1,79 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/widgets.dart'; +import 'package:mobile/models/group.dart'; +import 'package:mobile/services/api.dart'; + +class GroupController extends ChangeNotifier { + final Map> _selectedContacts = {}; + + Map> get selectedContacts => _selectedContacts; + + void addContacts(Map> contacts) { + _selectedContacts.clear(); + _selectedContacts.addAll(contacts); + notifyListeners(); + } + + // Group Creation + + final ApiService _api = ApiService(); + + bool _isLoading = false; + bool get isLoading => _isLoading; + + Future createGroup({ + required String groupName, + required List memberIds, + }) async { + if (_isLoading) return null; + + try { + _isLoading = true; + notifyListeners(); + + final createResponse = await _api.post( + '/groups', + data: {"name": groupName}, + ); + + if (createResponse.statusCode == 200 || + createResponse.statusCode == 201) { + final groupData = + createResponse.data['data'] ?? + createResponse.data['group'] ?? + createResponse.data; + + final Group newGroup = Group.fromJson(groupData); + + if (memberIds.isNotEmpty) { + final memberRequests = memberIds.map((userId) { + return _api.post( + '/groups/${newGroup.id}/members', + data: {"user_id": userId}, + ); + }); + + await Future.wait(memberRequests); + } + + return newGroup; + } + + return null; + } on DioException catch (e) { + debugPrint("Dio Error: ${e.message}"); + debugPrint("URL: ${e.requestOptions.uri}"); + + if (e.response != null) { + debugPrint("Payload: ${e.response?.data}"); + } + return null; + } catch (e) { + debugPrint("Error: $e"); + return null; + } finally { + _isLoading = false; + notifyListeners(); + } + } +} diff --git a/mobile/lib/services/api.dart b/mobile/lib/services/api.dart index 630c586..593b6e8 100644 --- a/mobile/lib/services/api.dart +++ b/mobile/lib/services/api.dart @@ -86,4 +86,12 @@ class ApiService { }, ); } + + Future post( + String path, { + dynamic data, + Map? queryParameters, + }) async { + return await _dio.post(path, data: data, queryParameters: queryParameters); + } } diff --git a/mobile/lib/widgets/custom_cards.dart b/mobile/lib/widgets/custom_cards.dart index 3b98536..deaba04 100644 --- a/mobile/lib/widgets/custom_cards.dart +++ b/mobile/lib/widgets/custom_cards.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_markdown_plus/flutter_markdown_plus.dart'; import 'package:provider/provider.dart'; import 'package:simple_rich_text/simple_rich_text.dart'; import '../models/conversation.dart';