Skip to content

Commit 67f583c

Browse files
dm page: added search within recent dm & to start a new dm or group conversation
Implemented the search functionality in the dm page which searches from within the recent conversations. Implemented starting a new dm with other user 1-to-1 or in a group both. the UI is similar to that in the figma design
1 parent 705797a commit 67f583c

File tree

2 files changed

+374
-35
lines changed

2 files changed

+374
-35
lines changed

lib/widgets/new_dm.dart

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import 'package:flutter/material.dart';
2+
3+
import '../api/model/model.dart';
4+
import '../model/narrow.dart';
5+
import 'content.dart';
6+
import 'message_list.dart';
7+
import 'page.dart';
8+
import 'store.dart';
9+
import 'theme.dart';
10+
11+
class NewDmScreen extends StatefulWidget {
12+
const NewDmScreen({super.key});
13+
14+
static Route<void> buildRoute({int? accountId, BuildContext? context}) {
15+
return MaterialAccountWidgetRoute(
16+
accountId: accountId,
17+
context: context,
18+
page: const NewDmScreen()
19+
);
20+
}
21+
22+
@override
23+
State<NewDmScreen> createState() => _NewDmScreenState();
24+
}
25+
26+
class _NewDmScreenState extends State<NewDmScreen> {
27+
final List<User> _selectedUsers = [];
28+
final TextEditingController _searchController = TextEditingController();
29+
final ScrollController _scrollController = ScrollController();
30+
31+
32+
List<User> _allUsers = [];
33+
bool _isLoading = true;
34+
bool _isDataFetched = false; // To ensure `_fetchUsers` is called only once
35+
36+
@override
37+
void initState() {
38+
super.initState();
39+
}
40+
41+
@override
42+
void didChangeDependencies() {
43+
super.didChangeDependencies();
44+
45+
if (!_isDataFetched) {
46+
_isDataFetched = true; // Avoid calling `_fetchUsers` multiple times
47+
_fetchUsers();
48+
}
49+
}
50+
51+
Future<void> _fetchUsers() async {
52+
setState(() {
53+
_isLoading = true;
54+
});
55+
56+
try {
57+
final store = PerAccountStoreWidget.of(context);
58+
final usersMap = store.users;
59+
setState(() {
60+
_allUsers = usersMap.values.toList();
61+
_isLoading = false;
62+
});
63+
print('Fetched ${_allUsers.length} users');
64+
} catch (error) {
65+
setState(() {
66+
_isLoading = false;
67+
});
68+
// Handle error appropriately
69+
print('Error fetching users: $error');
70+
}
71+
}
72+
73+
List<User> get _filteredUsers {
74+
final query = _searchController.text.toLowerCase();
75+
return _allUsers.where((user) =>
76+
!_selectedUsers.contains(user) &&
77+
user.fullName.toLowerCase().contains(query)
78+
).toList();
79+
}
80+
81+
void _handleUserSelect(User user) {
82+
setState(() {
83+
_selectedUsers.add(user);
84+
_searchController.clear();
85+
});
86+
Future.delayed(Duration(milliseconds: 10), () {
87+
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
88+
});
89+
}
90+
91+
void _handleUserRemove(User user) {
92+
setState(() {
93+
_selectedUsers.remove(user);
94+
});
95+
}
96+
97+
void _handleDone() {
98+
if (_selectedUsers.isNotEmpty) {
99+
final store = PerAccountStoreWidget.of(context);
100+
final narrow = DmNarrow.withOtherUsers(
101+
_selectedUsers.map((u) => u.userId),
102+
selfUserId: store.selfUserId,
103+
);
104+
Navigator.pushReplacement(context,
105+
MessageListPage.buildRoute(context: context, narrow: narrow));
106+
}
107+
}
108+
109+
@override
110+
Widget build(BuildContext context) {
111+
final screenHeight = MediaQuery.of(context).size.height;
112+
DesignVariables designVariables = DesignVariables.of(context);
113+
114+
return Scaffold(
115+
appBar: AppBar(
116+
leading: IconButton(
117+
icon: const Icon(Icons.arrow_back),
118+
onPressed: () => Navigator.pop(context),
119+
),
120+
title: const Text('New DM'),
121+
actions: [
122+
TextButton(
123+
onPressed: _selectedUsers.isEmpty ? null : _handleDone,
124+
child: Text(
125+
'Next',
126+
style: TextStyle(
127+
color: _selectedUsers.isEmpty
128+
? Colors.grey
129+
: designVariables.icon,
130+
),
131+
),
132+
),
133+
],
134+
),
135+
body: _isLoading? const Center(child: CircularProgressIndicator()) : Column(
136+
children: [
137+
Column(
138+
crossAxisAlignment: CrossAxisAlignment.start,
139+
children: [
140+
Container(
141+
color: const Color(0xff313131),
142+
constraints: BoxConstraints(
143+
minWidth: double.infinity,
144+
maxHeight: screenHeight * 0.2, // Limit height to 20% of screen
145+
),
146+
child: Padding(
147+
padding: const EdgeInsets.symmetric(horizontal: 8.0),
148+
child: SingleChildScrollView(
149+
controller: _scrollController,
150+
scrollDirection: Axis.vertical,
151+
child: Wrap(
152+
spacing: 5,
153+
children: [
154+
..._selectedUsers.map((user) => Chip(
155+
avatar: Avatar(userId: user.userId, size: 32, borderRadius: 3),
156+
label: Text(user.fullName),
157+
onDeleted: () => _handleUserRemove(user),
158+
backgroundColor: Color(0xFF40000000),
159+
)),
160+
SizedBox(
161+
width: 150,
162+
child: TextField(
163+
controller: _searchController,
164+
decoration: const InputDecoration(
165+
hintText: 'Add person',
166+
border: InputBorder.none,
167+
),
168+
onChanged: (_) => setState(() {}),
169+
),
170+
),
171+
],
172+
),
173+
),
174+
),
175+
),
176+
],
177+
),
178+
const Divider(height: 1),
179+
Expanded(
180+
child: ListView.builder(
181+
itemCount: _filteredUsers.length,
182+
itemBuilder: (context, index) {
183+
final user = _filteredUsers[index];
184+
return ListTile(
185+
leading: Row(
186+
mainAxisSize: MainAxisSize.min,
187+
children: [
188+
Icon(
189+
_selectedUsers.contains(user)
190+
? Icons.check_circle
191+
: Icons.circle_outlined,
192+
color: _selectedUsers.contains(user)
193+
? Theme.of(context).primaryColor
194+
: Colors.grey,
195+
),
196+
const SizedBox(width: 8), // Add spacing between the icon and avatar
197+
Avatar(userId: user.userId, size: 32, borderRadius: 3),
198+
],
199+
),
200+
title: Text(user.fullName),
201+
onTap: () => _handleUserSelect(user),
202+
);
203+
},
204+
),
205+
),
206+
207+
],
208+
),
209+
);
210+
}
211+
}

0 commit comments

Comments
 (0)