Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 59 additions & 23 deletions lib/core/viewmodels/main_page/chat/chat_inside_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:unn_mobile/core/misc/date_time_utilities/date_time_extensions.da
import 'package:unn_mobile/core/misc/objects_with_pagination.dart';
import 'package:unn_mobile/core/misc/user/current_user_sync_storage.dart';
import 'package:unn_mobile/core/models/common/file_data.dart';
import 'package:unn_mobile/core/models/dialog/base_dialog_info.dart';
import 'package:unn_mobile/core/models/dialog/dialog.dart';
import 'package:unn_mobile/core/models/dialog/message/enum/message_state.dart';
import 'package:unn_mobile/core/models/dialog/message/message.dart';
Expand All @@ -25,7 +26,9 @@ class ChatInsideViewModel extends BaseViewModel {

ChatScreenViewModel? _dialogsViewModel;

Dialog? _dialog;
BaseDialogInfo? _dialog;

int? chatId;

bool _hasError = false;

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

Dialog? get dialog => _dialog;
int get unreadMessagesCount =>
(_dialog is Dialog) ? (_dialog! as Dialog).unreadMessagesCount : 0;

BaseDialogInfo? get dialog => _dialog;
bool get hasError => _hasError;

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

Future<PaginatedResult<Message>?> getMessagesByDialogId(
String dialogId,
) async {
final messages =
await _messagesAggregator.fetchByDialogId(dialogId: dialogId);
if (messages == null) {
return null;
}
if (messages is PaginatedResultWithChatId<Message>) {
chatId = messages.chatId;
}
return messages;
}

FutureOr<void> getNewMessages() async {
if (_dialog == null) {
return;
}

final messages = await tryLoginAndRetrieveData<PaginatedResult<Message>>(
() => _messagesAggregator.fetchByChatId(chatId: _dialog!.chatId),
() => chatId == null
? getMessagesByDialogId(_dialog!.dialogId.stringValue)
: _messagesAggregator.fetchByChatId(chatId: chatId!),
() => null,
);
if (messages == null) {
_hasError = true;
return;
}
await _readMessages(_dialog!.chatId, messages.items);

if (chatId != null) {
await _readMessages(chatId!, messages.items);
}
final messagesToRemove = messages.items.length -
messages.items.reversed
.takeWhile(
Expand All @@ -97,8 +120,7 @@ class ChatInsideViewModel extends BaseViewModel {
if (refreshLoopRunning && !refreshLoopStopFlag) {
await _dialogsViewModel!.init();

_dialog = _dialogsViewModel!.dialogs
.firstWhere((d) => d.chatId == _dialog!.chatId);
fetchDialogFromRoot(chatId);

_dialogsViewModel!.notifyListeners();
}
Expand All @@ -114,7 +136,14 @@ class ChatInsideViewModel extends BaseViewModel {
..addAll(_partitionMessages(_unpartitionedMessages));
}

FutureOr<void> init(int chatId) async {
void fetchDialogFromRoot(int? chatId) {
_dialog = _dialogsViewModel!.dialogs.cast<BaseDialogInfo>().firstWhere(
(d) => d is Dialog && d.chatId == chatId,
orElse: () => _dialogsViewModel!.storedDialogInfo!,
);
}

FutureOr<void> init(int? chatId) async {
_isInitializing = true;
try {
await busyCallAsync(() => _init(chatId));
Expand All @@ -135,17 +164,21 @@ class ChatInsideViewModel extends BaseViewModel {
}
final messages =
await tryLoginAndRetrieveData<PaginatedResult<Message>?>(
() => _messagesAggregator.fetchByChatId(
chatId: _dialog!.chatId,
lastMessageId: _unpartitionedMessages.last.messageId,
),
() => chatId == null
? getMessagesByDialogId(_dialog!.dialogId.stringValue)
: _messagesAggregator.fetchByChatId(
chatId: chatId!,
lastMessageId: _unpartitionedMessages.last.messageId,
),
() => null,
);
if (messages == null) {
_hasError = true;
return;
}
await _readMessages(_dialog!.chatId, messages.items);
if (chatId != null) {
await _readMessages(chatId!, messages.items);
}
_unpartitionedMessages.addAll(messages.items);
_messages
..clear()
Expand All @@ -167,7 +200,6 @@ class ChatInsideViewModel extends BaseViewModel {
await Future.delayed(const Duration(seconds: 5));
if (!_isInitializing) {
await getNewMessages();

notifyListeners();
}
await refreshLoop(checkStartConditions: false);
Expand All @@ -176,7 +208,7 @@ class ChatInsideViewModel extends BaseViewModel {
FutureOr<bool> sendFiles(Iterable<String> uris, {String? text}) =>
_sendMessageWrapper<List<FileData>>(
() => _messagesAggregator.sendFiles(
chatId: _dialog!.chatId,
chatId: chatId!,
files: [for (final uri in uris) File(uri)],
text: text,
),
Expand All @@ -196,26 +228,29 @@ class ChatInsideViewModel extends BaseViewModel {
),
);

FutureOr<void> _init(int chatId) async {
FutureOr<void> _init(int? chatId) async {
_hasError = false;
_hasMessagesBefore = false;
_hasMessagesAfter = false;
this.chatId = chatId;
_messages.clear();
_unpartitionedMessages.clear();
_dialogsViewModel =
_routesViewModelFactory.getViewModelByType<ChatScreenViewModel>();
_dialog = _dialogsViewModel!.dialogs.firstWhere((d) => d.chatId == chatId);

fetchDialogFromRoot(chatId);
final messages = await tryLoginAndRetrieveData<PaginatedResult<Message>>(
() => _messagesAggregator.fetchByChatId(chatId: chatId),
() => this.chatId == null
? getMessagesByDialogId(_dialog!.dialogId.stringValue)
: _messagesAggregator.fetchByChatId(chatId: this.chatId!),
() => null,
);
if (messages == null) {
_hasError = true;
return;
}
await _readMessages(chatId, messages.items);

if (this.chatId != null) {
await _readMessages(this.chatId!, messages.items);
}
_unpartitionedMessages.addAll(messages.items.reversed);
_messages.addAll(_partitionMessages(messages.items.reversed));
_hasMessagesBefore = messages.hasPreviousPage;
Expand All @@ -226,7 +261,6 @@ class ChatInsideViewModel extends BaseViewModel {

List<List<List<Message>>> _partitionMessages(Iterable<Message> messages) {
const maxTimeDifference = 5;
final unreadMessagesCount = _dialog?.unreadMessagesCount ?? 0;
final List<List<List<Message>>> partitions = [];
for (final (index, message) in messages.indexed) {
final lastDatePartition = partitions.lastOrNull;
Expand Down Expand Up @@ -282,7 +316,9 @@ class ChatInsideViewModel extends BaseViewModel {
if (_dialog == null) {
return false;
}

if (chatId == null) {
return false;
}
final result = await tryLoginAndRetrieveData<T>(
sendFunction,
() => null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:async';
import 'package:unn_mobile/core/misc/authorisation/try_login_and_retrieve_data.dart';
import 'package:unn_mobile/core/misc/objects_with_pagination.dart';
import 'package:unn_mobile/core/misc/user/current_user_sync_storage.dart';
import 'package:unn_mobile/core/models/dialog/base_dialog_info.dart';
import 'package:unn_mobile/core/models/dialog/dialog.dart';
import 'package:unn_mobile/core/models/dialog/dialog_query_parameter.dart';
import 'package:unn_mobile/core/services/interfaces/dialog/dialog_service.dart';
Expand All @@ -21,6 +22,8 @@ class ChatScreenViewModel extends BaseViewModel {

final List<Dialog> _dialogs = [];

BaseDialogInfo? storedDialogInfo;

int? _currentUserId;

bool _hasError = false;
Expand Down
85 changes: 84 additions & 1 deletion lib/ui/views/main_page/chat/chat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import 'package:unn_mobile/core/constants/date_pattern.dart';
import 'package:unn_mobile/core/misc/date_time_utilities/date_time_extensions.dart';
import 'package:unn_mobile/core/misc/user/user_functions.dart';
import 'package:unn_mobile/core/models/dialog/dialog.dart' as d;
import 'package:unn_mobile/core/models/dialog/preview_dialog.dart';
import 'package:unn_mobile/core/services/interfaces/dialog/dialog_search_service.dart';
import 'package:unn_mobile/core/viewmodels/factories/main_page_routes_view_models_factory.dart';
import 'package:unn_mobile/core/viewmodels/main_page/chat/chat_screen_view_model.dart';
import 'package:unn_mobile/ui/views/base_view.dart';
Expand All @@ -35,12 +37,45 @@ class _ChatScreenViewState extends State<ChatScreenView> {
.getViewModelByRouteIndex<ChatScreenViewModel>(
widget.bottomRouteIndex!,
);

return BaseView<ChatScreenViewModel>(
builder: (context, model, child) => Scaffold(
appBar: AppBar(
title: const Text('Сообщения'),
leading: getSubpageLeading(widget.bottomRouteIndex),
actions: [
if (!model.isBusy)
SearchAnchor(
builder: (context, controller) => IconButton(
onPressed: () {
controller.openView();
},
icon: const Icon(Icons.search),
),
suggestionsBuilder: (context, controller) async {
final suggService =
Injector.appInstance.get<DialogSearchService>();
if (controller.text.length < 3) {
final history = await suggService.getHistory();
return history
?.where((e) => e.title.contains(controller.text))
.map(
(e) =>
searchItemTile(context, e, controller, model),
) ??
[];
}
final sugg = await suggService.search(controller.text);
final data = sugg
?.map(
(e) =>
searchItemTile(context, e, controller, model),
)
.toList() ??
[];
return data;
Comment on lines +54 to +75
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Как тебе такое?

suggestionsBuilder: (context, controller) async {
  final suggService = Injector.appInstance.get<DialogSearchService>();

  if (controller.text.length < 3) {
    final history = await suggService.getHistory();
    return [
      for (final e in history ?? const <PreviewDialog>[])
        if (e.title.contains(controller.text))
          searchItemTile(context, e, controller, model),
    ];
  }

  final results = await suggService.search(controller.text);
  return [
    for (final e in results ?? const <PreviewDialog>[])
      searchItemTile(context, e, controller, model),
  ];
},

},
),
],
),
body: Builder(
builder: (context) {
Expand Down Expand Up @@ -106,6 +141,54 @@ class _ChatScreenViewState extends State<ChatScreenView> {
onModelReady: (model) => model.init(),
);
}

Widget searchItemTile(
BuildContext context,
PreviewDialog dialog,
SearchController controller,
ChatScreenViewModel model,
) {
final theme = Theme.of(context);

return ListTile(
leading: CircleAvatar(
radius: MediaQuery.of(context).textScaler.scale(26.0),
foregroundImage: dialog.avatarUrl.isNotEmpty
? CachedNetworkImageProvider(dialog.avatarUrl)
: null,
child: dialog.avatarUrl.isEmpty
? FittedBox(
fit: BoxFit.cover,
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Text(
generateInitials(
dialog.title.split(' '),
),
style: theme.textTheme.headlineSmall!.copyWith(
color: theme.colorScheme.onSurface,
),
),
),
)
: null,
),
title: Text(
dialog.title,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.titleMedium,
),
enableFeedback: true,
visualDensity: VisualDensity.adaptivePlatformDensity,
onTap: () {
controller.closeView('');
model.storedDialogInfo = dialog;
GoRouter.of(context).go(
'${GoRouter.of(context).state.path}/stored',
);
},
);
}
}

class DialogInfo extends StatelessWidget {
Expand Down
10 changes: 5 additions & 5 deletions lib/ui/views/main_page/chat/chat_inside.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ import 'package:unn_mobile/ui/views/main_page/chat/widgets/message_group.dart';
import 'package:unn_mobile/ui/views/main_page/chat/widgets/send_field.dart';

class ChatInside extends StatefulWidget {
const ChatInside({required this.chatId, super.key});

final int chatId;
const ChatInside({this.chatId, super.key});

final int? chatId;
@override
State<ChatInside> createState() => _ChatInsideState();
}
Expand All @@ -31,7 +30,7 @@ class _ChatInsideState extends State<ChatInside> {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!hasScrolledOnce &&
model.dialog != null &&
model.dialog!.unreadMessagesCount > 0 &&
model.unreadMessagesCount > 0 &&
scrollController.hasClients) {
final renderBox = newMessagesKey.currentContext
?.findRenderObject() as RenderBox?;
Expand Down Expand Up @@ -142,7 +141,8 @@ class _ChatInsideState extends State<ChatInside> {
),
),
],
if (model.lastReadMessageId == null)
if (model.messages.isNotEmpty &&
model.lastReadMessageId == null)
_buildNewMessagesBar(),
if (model.isBusy)
const Center(
Expand Down
8 changes: 8 additions & 0 deletions lib/ui/views/main_page/main_page_routing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ class MainPageRouting {
bottomRouteIndex: 2,
),
subroutes: [
MainPageRouteData(
Icons.chat,
Icons.chat,
'Чат',
'stored',
builder: (_, state) => const ChatInside(),
userTypes: [],
),
MainPageRouteData(
Icons.chat,
Icons.chat,
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: unn_mobile
description: A mobile application for UNN Portal website
publish_to: 'none'

version: 0.6.0+375
version: 0.6.0+376

environment:
sdk: '>=3.1.2 <4.0.0'
Expand Down
Loading