Skip to content

Commit 3d732f6

Browse files
Merge pull request #65 from ChandruWritesCode/64-feat-merging-backend
#64 feat merging backend
2 parents 96ece1e + ba13c7f commit 3d732f6

9 files changed

Lines changed: 365 additions & 36 deletions

File tree

mobile/lib/controllers/chat.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@ class ChatController extends ChangeNotifier {
1919
bool isPeerTyping = false;
2020
bool isPeerOnline = false;
2121
bool isSearchLoading = false;
22+
bool _isWsInitialized = false;
2223

2324
Future<void> initSession(String token) async {
25+
if (_isWsInitialized) return;
26+
_isWsInitialized = true;
27+
2428
await _ws.connect(token);
2529
_ws.stream?.listen(
2630
(rawFrame) {
@@ -34,8 +38,12 @@ class ChatController extends ChangeNotifier {
3438
}
3539
},
3640
onError: (err) => debugPrint("WS Pipeline Error: $err"),
37-
onDone: () => _ws.disconnect(),
41+
onDone: () {
42+
_ws.disconnect();
43+
_isWsInitialized = false;
44+
},
3845
);
46+
3947
await loadInbox();
4048
}
4149

mobile/lib/main.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:mobile/providers/basic_providers.dart';
3+
import 'package:mobile/providers/group_controller_provider.dart';
34
import 'package:provider/provider.dart';
45
import 'core/constants.dart';
56
import 'controllers/auth.dart';
@@ -16,6 +17,7 @@ void main() async {
1617
ChangeNotifierProvider(create: (_) => BasicProviders()),
1718
ChangeNotifierProvider(create: (_) => AuthState()),
1819
ChangeNotifierProvider(create: (_) => ChatController()),
20+
ChangeNotifierProvider(create: (_) => GroupController()),
1921
],
2022
child: const MyApp(),
2123
),

mobile/lib/pages/select_contact_page.dart

Lines changed: 132 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import 'dart:async';
22
import 'package:flutter/material.dart';
33
import 'package:mobile/controllers/chat.dart';
4+
import 'package:mobile/pages/chat_page.dart';
5+
import 'package:mobile/providers/group_controller_provider.dart';
46
import 'package:provider/provider.dart';
57

68
class SelectContactPage extends StatefulWidget {
@@ -15,7 +17,9 @@ class _SelectContactPageState extends State<SelectContactPage> {
1517

1618
Timer? _debounceTimer;
1719
bool _isSearchOpen = false;
20+
1821
final _searchController = TextEditingController();
22+
final _groupNameController = TextEditingController();
1923

2024
void _onSearchChanged(String value, ChatController chatState) {
2125
if (_debounceTimer?.isActive ?? false) _debounceTimer!.cancel();
@@ -42,6 +46,7 @@ class _SelectContactPageState extends State<SelectContactPage> {
4246
void dispose() {
4347
_debounceTimer?.cancel();
4448
_searchController.dispose();
49+
_groupNameController.dispose();
4550
super.dispose();
4651
}
4752

@@ -270,7 +275,6 @@ class _SelectContactPageState extends State<SelectContactPage> {
270275
),
271276
),
272277
],
273-
274278
const Padding(
275279
padding: EdgeInsets.only(left: 16, top: 20, bottom: 8),
276280
child: Text(
@@ -282,7 +286,6 @@ class _SelectContactPageState extends State<SelectContactPage> {
282286
),
283287
),
284288
),
285-
286289
if (chatState.inbox.isNotEmpty)
287290
...chatState.inbox
288291
.where(
@@ -304,10 +307,136 @@ class _SelectContactPageState extends State<SelectContactPage> {
304307
),
305308
floatingActionButton: _selectedContacts.isNotEmpty
306309
? FloatingActionButton(
307-
onPressed: () {},
308310
backgroundColor: Colors.blue,
309311
elevation: 4,
310312
child: const Icon(Icons.arrow_forward, color: Colors.white),
313+
onPressed: () {
314+
context.read<GroupController>().addContacts(_selectedContacts);
315+
316+
showModalBottomSheet(
317+
context: context,
318+
isScrollControlled: true,
319+
shape: const RoundedRectangleBorder(
320+
borderRadius: BorderRadius.vertical(
321+
top: Radius.circular(16),
322+
),
323+
),
324+
builder: (BuildContext bottomSheetContext) {
325+
return Consumer<GroupController>(
326+
builder: (context, groupState, child) {
327+
return Padding(
328+
padding: EdgeInsets.only(
329+
bottom: MediaQuery.of(
330+
bottomSheetContext,
331+
).viewInsets.bottom,
332+
left: 16,
333+
right: 16,
334+
top: 24,
335+
),
336+
child: Column(
337+
mainAxisSize: MainAxisSize.min,
338+
children: [
339+
const Text(
340+
'New Group',
341+
style: TextStyle(
342+
fontSize: 18,
343+
fontWeight: FontWeight.bold,
344+
),
345+
),
346+
const SizedBox(height: 16),
347+
TextField(
348+
controller: _groupNameController,
349+
autofocus: true,
350+
decoration: const InputDecoration(
351+
hintText: 'Enter group name',
352+
border: OutlineInputBorder(),
353+
),
354+
),
355+
const SizedBox(height: 16),
356+
Row(
357+
mainAxisAlignment: MainAxisAlignment.end,
358+
children: [
359+
FilledButton(
360+
onPressed: groupState.isLoading
361+
? null
362+
: () async {
363+
final groupName =
364+
_groupNameController.text
365+
.trim();
366+
if (groupName.isEmpty) return;
367+
368+
final memberIds = _selectedContacts
369+
.keys
370+
.toList();
371+
372+
final newGroup = await context
373+
.read<GroupController>()
374+
.createGroup(
375+
groupName: groupName,
376+
memberIds: memberIds,
377+
);
378+
379+
if (newGroup != null) {
380+
if (!context.mounted) return;
381+
382+
context
383+
.read<ChatController>()
384+
.loadInbox();
385+
386+
Navigator.of(
387+
bottomSheetContext,
388+
).pop();
389+
Navigator.of(
390+
context,
391+
).pushReplacement(
392+
MaterialPageRoute(
393+
builder: (context) => ChatPage(
394+
chatUserId: newGroup
395+
.id,
396+
displayName: newGroup
397+
.name,
398+
),
399+
),
400+
);
401+
402+
context
403+
.read<ChatController>()
404+
.openChat(newGroup.id);
405+
} else {
406+
if (!context.mounted) return;
407+
ScaffoldMessenger.of(
408+
context,
409+
).showSnackBar(
410+
const SnackBar(
411+
content: Text(
412+
'Failed to create group',
413+
),
414+
),
415+
);
416+
}
417+
},
418+
child: groupState.isLoading
419+
? const SizedBox(
420+
height: 20,
421+
width: 20,
422+
child: CircularProgressIndicator(
423+
color: Colors.white,
424+
strokeWidth: 2,
425+
),
426+
)
427+
: const Text('Create'),
428+
),
429+
],
430+
),
431+
const SizedBox(height: 16),
432+
],
433+
),
434+
);
435+
},
436+
);
437+
},
438+
);
439+
},
311440
)
312441
: null,
313442
);

mobile/lib/pages/settings pages/accounts.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import 'package:flutter/material.dart';
22
import 'package:mobile/controllers/auth.dart';
3-
import 'package:mobile/controllers/chat.dart';
43
import 'package:mobile/providers/basic_providers.dart';
54
import 'package:mobile/main.dart';
65
import 'package:provider/provider.dart';
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import 'package:dio/dio.dart';
2+
import 'package:flutter/widgets.dart';
3+
import 'package:mobile/models/group.dart';
4+
import 'package:mobile/services/api.dart';
5+
6+
class GroupController extends ChangeNotifier {
7+
final Map<String, Map<String, String>> _selectedContacts = {};
8+
9+
Map<String, Map<String, String>> get selectedContacts => _selectedContacts;
10+
11+
void addContacts(Map<String, Map<String, String>> contacts) {
12+
_selectedContacts.clear();
13+
_selectedContacts.addAll(contacts);
14+
notifyListeners();
15+
}
16+
17+
// Group Creation
18+
19+
final ApiService _api = ApiService();
20+
21+
bool _isLoading = false;
22+
bool get isLoading => _isLoading;
23+
24+
Future<Group?> createGroup({
25+
required String groupName,
26+
required List<String> memberIds,
27+
}) async {
28+
if (_isLoading) return null;
29+
30+
try {
31+
_isLoading = true;
32+
notifyListeners();
33+
34+
final createResponse = await _api.post(
35+
'/groups',
36+
data: {"name": groupName},
37+
);
38+
39+
if (createResponse.statusCode == 200 ||
40+
createResponse.statusCode == 201) {
41+
final groupData =
42+
createResponse.data['data'] ??
43+
createResponse.data['group'] ??
44+
createResponse.data;
45+
46+
final Group newGroup = Group.fromJson(groupData);
47+
48+
if (memberIds.isNotEmpty) {
49+
final memberRequests = memberIds.map((userId) {
50+
return _api.post(
51+
'/groups/${newGroup.id}/members',
52+
data: {"user_id": userId},
53+
);
54+
});
55+
56+
await Future.wait(memberRequests);
57+
}
58+
59+
return newGroup;
60+
}
61+
62+
return null;
63+
} on DioException catch (e) {
64+
debugPrint("Dio Error: ${e.message}");
65+
debugPrint("URL: ${e.requestOptions.uri}");
66+
67+
if (e.response != null) {
68+
debugPrint("Payload: ${e.response?.data}");
69+
}
70+
return null;
71+
} catch (e) {
72+
debugPrint("Error: $e");
73+
return null;
74+
} finally {
75+
_isLoading = false;
76+
notifyListeners();
77+
}
78+
}
79+
}

mobile/lib/services/api.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,12 @@ class ApiService {
8686
},
8787
);
8888
}
89+
90+
Future<Response> post(
91+
String path, {
92+
dynamic data,
93+
Map<String, dynamic>? queryParameters,
94+
}) async {
95+
return await _dio.post(path, data: data, queryParameters: queryParameters);
96+
}
8997
}

0 commit comments

Comments
 (0)