Skip to content

Commit bee94cc

Browse files
KriseevMNamxobick
andauthored
UI и View Model для поиска диалогов (#604)
Co-authored-by: Yurin Andrey <100288192+Namxobick@users.noreply.github.com>
1 parent 3ea4ec3 commit bee94cc

File tree

9 files changed

+167
-33
lines changed

9 files changed

+167
-33
lines changed

lib/core/models/dialog/preview_group_dialog.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:unn_mobile/core/models/dialog/preview_dialog.dart';
77

88
class _PreviewGroupDialogJsonKeys {
99
static const String customData = 'customData';
10+
static const String chat = 'chat';
1011
}
1112

1213
class PreviewGroupDialog extends PreviewDialog {
@@ -27,7 +28,8 @@ class PreviewGroupDialog extends PreviewDialog {
2728
title: dialog.title,
2829
avatarUrl: dialog.avatarUrl,
2930
baseChatSetting: BaseChatSetting.fromJson(
30-
json[_PreviewGroupDialogJsonKeys.customData]! as JsonMap,
31+
(json[_PreviewGroupDialogJsonKeys.customData]!
32+
as JsonMap)[_PreviewGroupDialogJsonKeys.chat]! as JsonMap,
3133
),
3234
);
3335
}

lib/core/models/dialog/preview_user_dialog.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class _PreviewUserDialogJsonKeys {
88
static const String workPosition = 'workPosition';
99
static const String lastActivityDate = 'lastActivityDate';
1010
static const String customData = 'customData';
11+
static const String user = 'user';
1112
}
1213

1314
class PreviewUserDialog extends PreviewDialog {
@@ -32,7 +33,8 @@ class PreviewUserDialog extends PreviewDialog {
3233
avatarUrl: dialog.avatarUrl,
3334
lastActivityAt:
3435
lastActivityAt is String ? DateTime.tryParse(lastActivityAt) : null,
35-
workPosition: (json[_PreviewUserDialogJsonKeys.customData]!
36+
workPosition: ((json[_PreviewUserDialogJsonKeys.customData]!
37+
as JsonMap)[_PreviewUserDialogJsonKeys.user]!
3638
as JsonMap)[_PreviewUserDialogJsonKeys.workPosition]! as String,
3739
);
3840
}

lib/core/viewmodels/main_page/chat/chat_inside_view_model.dart

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:unn_mobile/core/misc/date_time_utilities/date_time_extensions.da
1111
import 'package:unn_mobile/core/misc/objects_with_pagination.dart';
1212
import 'package:unn_mobile/core/misc/user/current_user_sync_storage.dart';
1313
import 'package:unn_mobile/core/models/common/file_data.dart';
14+
import 'package:unn_mobile/core/models/dialog/base_dialog_info.dart';
1415
import 'package:unn_mobile/core/models/dialog/dialog.dart';
1516
import 'package:unn_mobile/core/models/dialog/message/enum/message_state.dart';
1617
import 'package:unn_mobile/core/models/dialog/message/message.dart';
@@ -25,7 +26,9 @@ class ChatInsideViewModel extends BaseViewModel {
2526

2627
ChatScreenViewModel? _dialogsViewModel;
2728

28-
Dialog? _dialog;
29+
BaseDialogInfo? _dialog;
30+
31+
int? chatId;
2932

3033
bool _hasError = false;
3134

@@ -55,7 +58,10 @@ class ChatInsideViewModel extends BaseViewModel {
5558
);
5659
int? get currentUserId => _currentUserSyncStorage.currentUserData?.bitrixId;
5760

58-
Dialog? get dialog => _dialog;
61+
int get unreadMessagesCount =>
62+
(_dialog is Dialog) ? (_dialog! as Dialog).unreadMessagesCount : 0;
63+
64+
BaseDialogInfo? get dialog => _dialog;
5965
bool get hasError => _hasError;
6066

6167
bool get hasMessagesAfter => _hasMessagesAfter;
@@ -71,21 +77,38 @@ class ChatInsideViewModel extends BaseViewModel {
7177
notifyListeners();
7278
}
7379

80+
Future<PaginatedResult<Message>?> getMessagesByDialogId(
81+
String dialogId,
82+
) async {
83+
final messages =
84+
await _messagesAggregator.fetchByDialogId(dialogId: dialogId);
85+
if (messages == null) {
86+
return null;
87+
}
88+
if (messages is PaginatedResultWithChatId<Message>) {
89+
chatId = messages.chatId;
90+
}
91+
return messages;
92+
}
93+
7494
FutureOr<void> getNewMessages() async {
7595
if (_dialog == null) {
7696
return;
7797
}
7898

7999
final messages = await tryLoginAndRetrieveData<PaginatedResult<Message>>(
80-
() => _messagesAggregator.fetchByChatId(chatId: _dialog!.chatId),
100+
() => chatId == null
101+
? getMessagesByDialogId(_dialog!.dialogId.stringValue)
102+
: _messagesAggregator.fetchByChatId(chatId: chatId!),
81103
() => null,
82104
);
83105
if (messages == null) {
84106
_hasError = true;
85107
return;
86108
}
87-
await _readMessages(_dialog!.chatId, messages.items);
88-
109+
if (chatId != null) {
110+
await _readMessages(chatId!, messages.items);
111+
}
89112
final messagesToRemove = messages.items.length -
90113
messages.items.reversed
91114
.takeWhile(
@@ -97,8 +120,7 @@ class ChatInsideViewModel extends BaseViewModel {
97120
if (refreshLoopRunning && !refreshLoopStopFlag) {
98121
await _dialogsViewModel!.init();
99122

100-
_dialog = _dialogsViewModel!.dialogs
101-
.firstWhere((d) => d.chatId == _dialog!.chatId);
123+
fetchDialogFromRoot(chatId);
102124

103125
_dialogsViewModel!.notifyListeners();
104126
}
@@ -114,7 +136,14 @@ class ChatInsideViewModel extends BaseViewModel {
114136
..addAll(_partitionMessages(_unpartitionedMessages));
115137
}
116138

117-
FutureOr<void> init(int chatId) async {
139+
void fetchDialogFromRoot(int? chatId) {
140+
_dialog = _dialogsViewModel!.dialogs.cast<BaseDialogInfo>().firstWhere(
141+
(d) => d is Dialog && d.chatId == chatId,
142+
orElse: () => _dialogsViewModel!.storedDialogInfo!,
143+
);
144+
}
145+
146+
FutureOr<void> init(int? chatId) async {
118147
_isInitializing = true;
119148
try {
120149
await busyCallAsync(() => _init(chatId));
@@ -135,17 +164,21 @@ class ChatInsideViewModel extends BaseViewModel {
135164
}
136165
final messages =
137166
await tryLoginAndRetrieveData<PaginatedResult<Message>?>(
138-
() => _messagesAggregator.fetchByChatId(
139-
chatId: _dialog!.chatId,
140-
lastMessageId: _unpartitionedMessages.last.messageId,
141-
),
167+
() => chatId == null
168+
? getMessagesByDialogId(_dialog!.dialogId.stringValue)
169+
: _messagesAggregator.fetchByChatId(
170+
chatId: chatId!,
171+
lastMessageId: _unpartitionedMessages.last.messageId,
172+
),
142173
() => null,
143174
);
144175
if (messages == null) {
145176
_hasError = true;
146177
return;
147178
}
148-
await _readMessages(_dialog!.chatId, messages.items);
179+
if (chatId != null) {
180+
await _readMessages(chatId!, messages.items);
181+
}
149182
_unpartitionedMessages.addAll(messages.items);
150183
_messages
151184
..clear()
@@ -167,7 +200,6 @@ class ChatInsideViewModel extends BaseViewModel {
167200
await Future.delayed(const Duration(seconds: 5));
168201
if (!_isInitializing) {
169202
await getNewMessages();
170-
171203
notifyListeners();
172204
}
173205
await refreshLoop(checkStartConditions: false);
@@ -176,7 +208,7 @@ class ChatInsideViewModel extends BaseViewModel {
176208
FutureOr<bool> sendFiles(Iterable<String> uris, {String? text}) =>
177209
_sendMessageWrapper<List<FileData>>(
178210
() => _messagesAggregator.sendFiles(
179-
chatId: _dialog!.chatId,
211+
chatId: chatId!,
180212
files: [for (final uri in uris) File(uri)],
181213
text: text,
182214
),
@@ -196,26 +228,29 @@ class ChatInsideViewModel extends BaseViewModel {
196228
),
197229
);
198230

199-
FutureOr<void> _init(int chatId) async {
231+
FutureOr<void> _init(int? chatId) async {
200232
_hasError = false;
201233
_hasMessagesBefore = false;
202234
_hasMessagesAfter = false;
235+
this.chatId = chatId;
203236
_messages.clear();
204237
_unpartitionedMessages.clear();
205238
_dialogsViewModel =
206239
_routesViewModelFactory.getViewModelByType<ChatScreenViewModel>();
207-
_dialog = _dialogsViewModel!.dialogs.firstWhere((d) => d.chatId == chatId);
208-
240+
fetchDialogFromRoot(chatId);
209241
final messages = await tryLoginAndRetrieveData<PaginatedResult<Message>>(
210-
() => _messagesAggregator.fetchByChatId(chatId: chatId),
242+
() => this.chatId == null
243+
? getMessagesByDialogId(_dialog!.dialogId.stringValue)
244+
: _messagesAggregator.fetchByChatId(chatId: this.chatId!),
211245
() => null,
212246
);
213247
if (messages == null) {
214248
_hasError = true;
215249
return;
216250
}
217-
await _readMessages(chatId, messages.items);
218-
251+
if (this.chatId != null) {
252+
await _readMessages(this.chatId!, messages.items);
253+
}
219254
_unpartitionedMessages.addAll(messages.items.reversed);
220255
_messages.addAll(_partitionMessages(messages.items.reversed));
221256
_hasMessagesBefore = messages.hasPreviousPage;
@@ -226,7 +261,6 @@ class ChatInsideViewModel extends BaseViewModel {
226261

227262
List<List<List<Message>>> _partitionMessages(Iterable<Message> messages) {
228263
const maxTimeDifference = 5;
229-
final unreadMessagesCount = _dialog?.unreadMessagesCount ?? 0;
230264
final List<List<List<Message>>> partitions = [];
231265
for (final (index, message) in messages.indexed) {
232266
final lastDatePartition = partitions.lastOrNull;
@@ -282,7 +316,9 @@ class ChatInsideViewModel extends BaseViewModel {
282316
if (_dialog == null) {
283317
return false;
284318
}
285-
319+
if (chatId == null) {
320+
return false;
321+
}
286322
final result = await tryLoginAndRetrieveData<T>(
287323
sendFunction,
288324
() => null,

lib/core/viewmodels/main_page/chat/chat_screen_view_model.dart

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ import 'dart:async';
66
import 'package:unn_mobile/core/misc/authorisation/try_login_and_retrieve_data.dart';
77
import 'package:unn_mobile/core/misc/objects_with_pagination.dart';
88
import 'package:unn_mobile/core/misc/user/current_user_sync_storage.dart';
9+
import 'package:unn_mobile/core/models/dialog/base_dialog_info.dart';
910
import 'package:unn_mobile/core/models/dialog/dialog.dart';
1011
import 'package:unn_mobile/core/models/dialog/dialog_query_parameter.dart';
12+
import 'package:unn_mobile/core/models/dialog/preview_dialog.dart';
13+
import 'package:unn_mobile/core/services/interfaces/dialog/dialog_search_service.dart';
1114
import 'package:unn_mobile/core/services/interfaces/dialog/dialog_service.dart';
1215
import 'package:unn_mobile/core/viewmodels/base_view_model.dart';
1316

@@ -19,15 +22,23 @@ class ChatScreenViewModel extends BaseViewModel {
1922

2023
final CurrentUserSyncStorage _currentUserSyncStorage;
2124

25+
final DialogSearchService _searchService;
26+
2227
final List<Dialog> _dialogs = [];
2328

29+
BaseDialogInfo? storedDialogInfo;
30+
2431
int? _currentUserId;
2532

2633
bool _hasError = false;
2734

2835
bool _hasMoreDialogs = false;
2936

30-
ChatScreenViewModel(this._dialogService, this._currentUserSyncStorage);
37+
ChatScreenViewModel(
38+
this._dialogService,
39+
this._currentUserSyncStorage,
40+
this._searchService,
41+
);
3142

3243
int? get currentUserId => _currentUserId;
3344

@@ -85,4 +96,12 @@ class ChatScreenViewModel extends BaseViewModel {
8596

8697
_currentUserId = _currentUserSyncStorage.currentUserData?.bitrixId;
8798
}
99+
100+
Future<Iterable<PreviewDialog>> getSuggestions(String query) async {
101+
if (query.length < 3) {
102+
final history = await _searchService.getHistory();
103+
return history?.where((e) => e.title.contains(query)) ?? [];
104+
}
105+
return await _searchService.search(query) ?? [];
106+
}
88107
}

lib/load_services.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,7 @@ void registerDependencies() {
761761
() => ChatScreenViewModel(
762762
get<DialogService>(),
763763
get<CurrentUserSyncStorage>(),
764+
get<DialogSearchService>(),
764765
),
765766
)
766767
..registerDependency(

lib/ui/views/main_page/chat/chat.dart

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:unn_mobile/core/constants/date_pattern.dart';
1010
import 'package:unn_mobile/core/misc/date_time_utilities/date_time_extensions.dart';
1111
import 'package:unn_mobile/core/misc/user/user_functions.dart';
1212
import 'package:unn_mobile/core/models/dialog/dialog.dart' as d;
13+
import 'package:unn_mobile/core/models/dialog/preview_dialog.dart';
1314
import 'package:unn_mobile/core/viewmodels/factories/main_page_routes_view_models_factory.dart';
1415
import 'package:unn_mobile/core/viewmodels/main_page/chat/chat_screen_view_model.dart';
1516
import 'package:unn_mobile/ui/views/base_view.dart';
@@ -35,12 +36,29 @@ class _ChatScreenViewState extends State<ChatScreenView> {
3536
.getViewModelByRouteIndex<ChatScreenViewModel>(
3637
widget.bottomRouteIndex!,
3738
);
38-
3939
return BaseView<ChatScreenViewModel>(
4040
builder: (context, model, child) => Scaffold(
4141
appBar: AppBar(
4242
title: const Text('Сообщения'),
4343
leading: getSubpageLeading(widget.bottomRouteIndex),
44+
actions: [
45+
if (!model.isBusy)
46+
SearchAnchor(
47+
builder: (context, controller) => IconButton(
48+
onPressed: () {
49+
controller.openView();
50+
},
51+
icon: const Icon(Icons.search),
52+
),
53+
suggestionsBuilder: (context, controller) async {
54+
final suggestions =
55+
await model.getSuggestions(controller.text);
56+
return suggestions.map(
57+
(e) => searchItemTile(context, e, controller, model),
58+
);
59+
},
60+
),
61+
],
4462
),
4563
body: Builder(
4664
builder: (context) {
@@ -106,6 +124,54 @@ class _ChatScreenViewState extends State<ChatScreenView> {
106124
onModelReady: (model) => model.init(),
107125
);
108126
}
127+
128+
Widget searchItemTile(
129+
BuildContext context,
130+
PreviewDialog dialog,
131+
SearchController controller,
132+
ChatScreenViewModel model,
133+
) {
134+
final theme = Theme.of(context);
135+
136+
return ListTile(
137+
leading: CircleAvatar(
138+
radius: MediaQuery.of(context).textScaler.scale(26.0),
139+
foregroundImage: dialog.avatarUrl.isNotEmpty
140+
? CachedNetworkImageProvider(dialog.avatarUrl)
141+
: null,
142+
child: dialog.avatarUrl.isEmpty
143+
? FittedBox(
144+
fit: BoxFit.cover,
145+
child: Padding(
146+
padding: const EdgeInsets.all(4.0),
147+
child: Text(
148+
generateInitials(
149+
dialog.title.split(' '),
150+
),
151+
style: theme.textTheme.headlineSmall!.copyWith(
152+
color: theme.colorScheme.onSurface,
153+
),
154+
),
155+
),
156+
)
157+
: null,
158+
),
159+
title: Text(
160+
dialog.title,
161+
overflow: TextOverflow.ellipsis,
162+
style: theme.textTheme.titleMedium,
163+
),
164+
enableFeedback: true,
165+
visualDensity: VisualDensity.adaptivePlatformDensity,
166+
onTap: () {
167+
controller.closeView('');
168+
model.storedDialogInfo = dialog;
169+
GoRouter.of(context).go(
170+
'${GoRouter.of(context).state.path}/stored',
171+
);
172+
},
173+
);
174+
}
109175
}
110176

111177
class DialogInfo extends StatelessWidget {

0 commit comments

Comments
 (0)