diff --git a/Softeq.XToolkit.Chat.Droid/Listeners/LoadMoreScrollListener.cs b/Softeq.XToolkit.Chat.Droid/Listeners/LoadMoreScrollListener.cs new file mode 100644 index 0000000..d0c86b1 --- /dev/null +++ b/Softeq.XToolkit.Chat.Droid/Listeners/LoadMoreScrollListener.cs @@ -0,0 +1,42 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using Android.Support.V7.Widget; +using Softeq.XToolkit.Common; + +namespace Softeq.XToolkit.Chat.Droid.Listeners +{ + public class LoadMoreScrollListener : RecyclerView.OnScrollListener + { + private const int VisibleThreshold = 5; + + private readonly TaskReference _onScrolled; + + private bool _isBusy; + + public LoadMoreScrollListener(TaskReference onScrolled) + { + _onScrolled = onScrolled ?? throw new ArgumentNullException(nameof(onScrolled)); + } + + public override async void OnScrolled(RecyclerView recyclerView, int dx, int dy) + { + base.OnScrolled(recyclerView, dx, dy); + + var linearLayoutManager = (LinearLayoutManager)recyclerView.GetLayoutManager(); + + var totalItemCount = linearLayoutManager.ItemCount; + var lastVisibleItem = linearLayoutManager.FindLastVisibleItemPosition(); + + if (!_isBusy && totalItemCount <= (lastVisibleItem + VisibleThreshold)) + { + _isBusy = true; + + await _onScrolled.RunAsync(); + + _isBusy = false; + } + } + } +} diff --git a/Softeq.XToolkit.Chat.Droid/Resources/layout/activity_chat_create.xml b/Softeq.XToolkit.Chat.Droid/Resources/layout/activity_chat_create.xml index 77d70c4..bf4b801 100644 --- a/Softeq.XToolkit.Chat.Droid/Resources/layout/activity_chat_create.xml +++ b/Softeq.XToolkit.Chat.Droid/Resources/layout/activity_chat_create.xml @@ -34,6 +34,8 @@ android:layout_height="wrap_content" android:layout_marginTop="24dp" android:layout_toRightOf="@+id/iv_chat_photo" + android:maxLines="1" + android:inputType="text" android:hint="Group Name" android:textSize="20sp" /> diff --git a/Softeq.XToolkit.Chat.Droid/Resources/layout/dialog_select_members.xml b/Softeq.XToolkit.Chat.Droid/Resources/layout/dialog_select_members.xml index 677543b..e5e84c0 100644 --- a/Softeq.XToolkit.Chat.Droid/Resources/layout/dialog_select_members.xml +++ b/Softeq.XToolkit.Chat.Droid/Resources/layout/dialog_select_members.xml @@ -22,6 +22,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/transparent" + android:maxLines="1" + android:inputType="text" android:hint="Search" /> 27.0.2.1 - 2.4.3.840 + 2.4.4.859 @@ -94,6 +94,7 @@ + diff --git a/Softeq.XToolkit.Chat.Droid/StyleHelper.cs b/Softeq.XToolkit.Chat.Droid/StyleHelper.cs index 55e71e4..8ad9602 100644 --- a/Softeq.XToolkit.Chat.Droid/StyleHelper.cs +++ b/Softeq.XToolkit.Chat.Droid/StyleHelper.cs @@ -37,6 +37,8 @@ public interface IChatDroidStyle int UnderlinedBg { get; } int CheckedIcon { get; } int UnCheckedIcon { get; } + int UnreadMessagesCountColor { get; } + int UnreadMutedMessagesCountColor { get; } } public abstract class ChatDroidStyleBase : IChatDroidStyle @@ -62,5 +64,7 @@ public abstract class ChatDroidStyleBase : IChatDroidStyle public abstract int UnderlinedBg { get; } public abstract int CheckedIcon { get; } public abstract int UnCheckedIcon { get; } + public abstract int UnreadMessagesCountColor { get; } + public abstract int UnreadMutedMessagesCountColor { get; } } } \ No newline at end of file diff --git a/Softeq.XToolkit.Chat.Droid/ViewHolders/ChatViewHolder.cs b/Softeq.XToolkit.Chat.Droid/ViewHolders/ChatViewHolder.cs index 9df0b6a..646d016 100644 --- a/Softeq.XToolkit.Chat.Droid/ViewHolders/ChatViewHolder.cs +++ b/Softeq.XToolkit.Chat.Droid/ViewHolders/ChatViewHolder.cs @@ -6,23 +6,23 @@ using Android.Graphics.Drawables; using Android.Views; using Android.Widget; +using Android.Support.V4.Content; using FFImageLoading.Cross; using FFImageLoading.Transformations; -using FFImageLoading.Work; using Softeq.XToolkit.Bindings; using Softeq.XToolkit.Chat.Models; using Softeq.XToolkit.Chat.ViewModels; using Softeq.XToolkit.Common; using Softeq.XToolkit.Common.Command; using Softeq.XToolkit.Common.Droid.Converters; +using Softeq.XToolkit.WhiteLabel.Droid.Controls; using Softeq.XToolkit.WhiteLabel.Droid.Extensions; namespace Softeq.XToolkit.Chat.Droid.ViewHolders { public class ChatViewHolder : BindableViewHolder { - private const string UnreadMessagesCountColor = "#5bc6c9"; - private const string UnreadMutedMessagesCountColor = "#b4b4b4"; + //TODO: move this to resources private const string ChatStatusDefaultColor = "#dedede"; private readonly WeakAction _selectChatAction; @@ -71,8 +71,12 @@ public override void BindViewModel(ChatSummaryViewModel viewModel) ChatPhotoImageView.LoadImageWithTextPlaceholder( _viewModelRef.Target.ChatPhotoUrl, _viewModelRef.Target.ChatName, - StyleHelper.Style.ChatAvatarStyles, - (TaskParameter x) => x.Transform(new CircleTransformation())); + new AvatarPlaceholderDrawable.AvatarStyles + { + BackgroundHexColors = StyleHelper.Style.ChatAvatarStyles.BackgroundHexColors, + Size = new System.Drawing.Size(45, 45) + }, + x => x.Transform(new CircleTransformation())); })); Bindings.Add(this.SetBinding(() => _viewModelRef.Target.UnreadMessageCount).WhenSourceChanges(() => @@ -88,8 +92,13 @@ public override void BindViewModel(ChatSummaryViewModel viewModel) { if (UnreadMessageCountTextView != null) { - var color = _viewModelRef.Target.IsMuted ? UnreadMutedMessagesCountColor : UnreadMessagesCountColor; - UnreadMessageCountTextView.Background = CreateBackgroundWithCornerRadius(Color.ParseColor(color), 56f); + var colorResId = _viewModelRef.Target.IsMuted + ? StyleHelper.Style.UnreadMutedMessagesCountColor + : StyleHelper.Style.UnreadMessagesCountColor; + + var color = ContextCompat.GetColor(UnreadMessageCountTextView.Context, colorResId); + + UnreadMessageCountTextView.Background = CreateBackgroundWithCornerRadius(color, 56f); } })); @@ -115,7 +124,7 @@ public override void BindViewModel(ChatSummaryViewModel viewModel) } // TODO: move to XToolkit.Common - private static Drawable CreateBackgroundWithCornerRadius(Color color, float radius) + private static Drawable CreateBackgroundWithCornerRadius(int color, float radius) { var drawable = new GradientDrawable(); diff --git a/Softeq.XToolkit.Chat.Droid/ViewHolders/ConversationViewHolder.cs b/Softeq.XToolkit.Chat.Droid/ViewHolders/ConversationViewHolder.cs index e1dfe2b..91b30ca 100644 --- a/Softeq.XToolkit.Chat.Droid/ViewHolders/ConversationViewHolder.cs +++ b/Softeq.XToolkit.Chat.Droid/ViewHolders/ConversationViewHolder.cs @@ -113,7 +113,10 @@ public override void BindViewModel(ChatMessageViewModel viewModel) SenderPhotoImageView.LoadImageWithTextPlaceholder( _viewModelRef.Target.SenderPhotoUrl, _viewModelRef.Target.SenderName, - StyleHelper.Style.ChatAvatarStyles, + new WhiteLabel.Droid.Controls.AvatarPlaceholderDrawable.AvatarStyles { + BackgroundHexColors = StyleHelper.Style.ChatAvatarStyles.BackgroundHexColors, + Size = new System.Drawing.Size(35, 35) + }, (TaskParameter x) => x.Transform(new CircleTransformation())); } diff --git a/Softeq.XToolkit.Chat.Droid/Views/AddContactsDialogFragment.cs b/Softeq.XToolkit.Chat.Droid/Views/AddContactsDialogFragment.cs index 611693d..38d3c4c 100644 --- a/Softeq.XToolkit.Chat.Droid/Views/AddContactsDialogFragment.cs +++ b/Softeq.XToolkit.Chat.Droid/Views/AddContactsDialogFragment.cs @@ -12,6 +12,8 @@ using Softeq.XToolkit.Common.Command; using Softeq.XToolkit.WhiteLabel.Droid.Controls; using Softeq.XToolkit.WhiteLabel.Droid.Dialogs; +using Softeq.XToolkit.Chat.Droid.Listeners; +using Softeq.XToolkit.Common; namespace Softeq.XToolkit.Chat.Droid.Views { @@ -37,11 +39,7 @@ public override void OnViewCreated(View view, Bundle savedInstanceState) View.SetBackgroundResource(StyleHelper.Style.ContentColor); - _navigationBarView = View.FindViewById(Resource.Id.dialog_select_members_nav_bar); - _navigationBarView.SetLeftButton(StyleHelper.Style.NavigationBarBackButtonIcon, new RelayCommand(Close)); - _navigationBarView.SetRightButton(ViewModel.Resources.Done, ViewModel.DoneCommand); - _navigationBarView.SetTitle(ViewModel.Title); - _navigationBarView.SetBackground(StyleHelper.Style.ContentColor); + InitNavigationBarView(view); var searchContainer = View.FindViewById(Resource.Id.dialog_select_members_serch_container); searchContainer.SetBackgroundResource(StyleHelper.Style.UnderlinedBg); @@ -49,7 +47,43 @@ public override void OnViewCreated(View view, Bundle savedInstanceState) _editText = View.FindViewById(Resource.Id.dialog_select_members_serch_text); _editText.Hint = ViewModel.Resources.Search; - _addedMembers = View.FindViewById(Resource.Id.dialog_select_members_added_members); + InitSelectedRecyclerView(view); + InitFilteredRecyclerView(view); + } + + protected override void DoAttachBindings() + { + base.DoAttachBindings(); + + Bindings.Add(this.SetBinding(() => ViewModel.ContactNameSearchQuery, () => _editText.Text, BindingMode.TwoWay)); + Bindings.Add(this.SetBinding(() => ViewModel.HasSelectedContacts).WhenSourceChanges(() => + { + _addedMembers.Visibility = ViewModel.HasSelectedContacts + ? ViewStates.Visible + : ViewStates.Gone; + })); + } + + public override void OnDestroy() + { + base.OnDestroy(); + + _filteredAdapter.Dispose(); + _selectedAdapter.Dispose(); + } + + private void InitNavigationBarView(View view) + { + _navigationBarView = view.FindViewById(Resource.Id.dialog_select_members_nav_bar); + _navigationBarView.SetLeftButton(StyleHelper.Style.NavigationBarBackButtonIcon, new RelayCommand(Close)); + _navigationBarView.SetRightButton(ViewModel.Resources.Done, ViewModel.DoneCommand); + _navigationBarView.SetTitle(ViewModel.Title); + _navigationBarView.SetBackground(StyleHelper.Style.ContentColor); + } + + private void InitSelectedRecyclerView(View view) + { + _addedMembers = view.FindViewById(Resource.Id.dialog_select_members_added_members); _addedMembers.SetLayoutManager(new LinearLayoutManager(Context, LinearLayoutManager.Horizontal, false)); _selectedAdapter = new ObservableRecyclerViewAdapter( ViewModel.SelectedContacts, @@ -62,14 +96,17 @@ public override void OnViewCreated(View view, Bundle savedInstanceState) { var viewHolder = (SelectedContactViewHolder)holder; viewHolder.Bind(item); - }); + }); _addedMembers.SetAdapter(_selectedAdapter); + } - _filteredMembers = View.FindViewById(Resource.Id.dialog_select_members_filtered_members); + private void InitFilteredRecyclerView(View view) + { + _filteredMembers = view.FindViewById(Resource.Id.dialog_select_members_filtered_members); _filteredMembers.SetLayoutManager(new LinearLayoutManager(Context)); _filteredMembers.AddItemDecoration(new DividerItemDecoration(Context, DividerItemDecoration.Vertical)); _filteredAdapter = new ObservableRecyclerViewAdapter( - ViewModel.FoundContacts, + ViewModel.PaginationViewModel.Items, (parent, i) => { var v = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.view_holder_member_filter_item, parent, false); @@ -81,32 +118,15 @@ public override void OnViewCreated(View view, Bundle savedInstanceState) viewHolder.Bind(item); }); _filteredMembers.SetAdapter(_filteredAdapter); - } - - protected override void DoAttachBindings() - { - base.DoAttachBindings(); - - this.SetBinding(() => ViewModel.ContactNameSearchQuery, () => _editText.Text, BindingMode.TwoWay); - this.SetBinding(() => ViewModel.HasSelectedContacts).WhenSourceChanges(() => - { - _addedMembers.Visibility = ViewModel.HasSelectedContacts - ? ViewStates.Visible - : ViewStates.Gone; - }); - } - - public override void OnDestroy() - { - base.OnDestroy(); + _filteredMembers.AddOnScrollListener(new LoadMoreScrollListener(new TaskReference(async () => + { + await ViewModel.PaginationViewModel.LoadNextPageAsync(); + }))); + } - _filteredAdapter.Dispose(); - _selectedAdapter.Dispose(); - } - private void Close() { ViewModel.DialogComponent.CloseCommand.Execute(false); - } + } } } diff --git a/Softeq.XToolkit.Chat.Droid/Views/ChatDetailsActivity.cs b/Softeq.XToolkit.Chat.Droid/Views/ChatDetailsActivity.cs index 466d842..939c32b 100644 --- a/Softeq.XToolkit.Chat.Droid/Views/ChatDetailsActivity.cs +++ b/Softeq.XToolkit.Chat.Droid/Views/ChatDetailsActivity.cs @@ -97,7 +97,11 @@ protected override void DoAttachBindings() _chatPhotoImageView.LoadImageWithTextPlaceholder( ViewModel.Summary.AvatarUrl, ViewModel.Summary.Name, - StyleHelper.Style.ChatAvatarStyles, + new AvatarPlaceholderDrawable.AvatarStyles + { + BackgroundHexColors = StyleHelper.Style.ChatAvatarStyles.BackgroundHexColors, + Size = new System.Drawing.Size(64, 64) + }, x => x.Transform(new CircleTransformation())); }); })); diff --git a/Softeq.XToolkit.Chat.Droid/Views/ChatMessagesActivity.cs b/Softeq.XToolkit.Chat.Droid/Views/ChatMessagesActivity.cs index 27928b2..65d33f1 100644 --- a/Softeq.XToolkit.Chat.Droid/Views/ChatMessagesActivity.cs +++ b/Softeq.XToolkit.Chat.Droid/Views/ChatMessagesActivity.cs @@ -46,7 +46,7 @@ public class ChatMessagesActivity : ActivityBase private ImageButton _editingMessageCloseButton; private ImageButton _scrollDownImageButton; private ContextMenuComponent _contextMenuComponent; - private bool _shouldSendStateMessageToChat; + //private bool _shouldSendStateMessageToChat; private bool _isAdapterSourceInitialized; private bool _isAutoScrollToFooterEnabled = true; private ImagePicker _imagePicker; @@ -113,10 +113,10 @@ protected override void OnCreate(Bundle savedInstanceState) protected override void OnPause() { - if (_shouldSendStateMessageToChat) - { - Messenger.Default.Send(new ChatInBackgroundMessage()); - } + //if (_shouldSendStateMessageToChat) + //{ + // Messenger.Default.Send(new ChatInBackgroundMessage()); + //} KeyboardService.HideSoftKeyboard(_messageEditText); @@ -127,22 +127,23 @@ protected override void OnResume() { base.OnResume(); - if (_shouldSendStateMessageToChat) - { - Messenger.Default.Send(new ChatInForegroundMessage()); - } - else - { - _shouldSendStateMessageToChat = true; - } + // TODO YP: think about disconnecting when app in background + //if (_shouldSendStateMessageToChat) + //{ + // Messenger.Default.Send(new ChatInForegroundMessage()); + //} + //else + //{ + // _shouldSendStateMessageToChat = true; + //} } - public override void OnBackPressed() - { - _shouldSendStateMessageToChat = false; + //public override void OnBackPressed() + //{ + // _shouldSendStateMessageToChat = false; - base.OnBackPressed(); - } + // base.OnBackPressed(); + //} protected override void OnDestroy() { @@ -301,6 +302,7 @@ private void CloseEditImageContainer() { _editImageContainer.Visibility = ViewStates.Gone; _previewImageKey = null; + _imagePicker.ViewModel.ImageCacheKey = null; _imagePreview.SetImageDrawable(null); }); } diff --git a/Softeq.XToolkit.Chat.Droid/Views/ChatsListFragment.cs b/Softeq.XToolkit.Chat.Droid/Views/ChatsListFragment.cs index d52c110..4a6af9a 100644 --- a/Softeq.XToolkit.Chat.Droid/Views/ChatsListFragment.cs +++ b/Softeq.XToolkit.Chat.Droid/Views/ChatsListFragment.cs @@ -102,14 +102,14 @@ private BindableViewHolder CreateChatViewHolder((ViewGroup private void ConfigureSwipeForViewHolder(RecyclerView.ViewHolder viewHolder, int position, ICollection actions) { - actions.Add(new SimpleSwipeActionView(ViewModel.LeaveChatOptionText, _swipeLeaveActionViewOptions, pos => + actions.Add(new SimpleSwipeActionView(ViewModel.LocalizedStrings.Leave, _swipeLeaveActionViewOptions, pos => { ViewModel.LeaveChatCommand.Execute(ViewModel.Chats[pos]); })); if (ViewModel.Chats[position].IsCreatedByMe) { - actions.Add(new SimpleSwipeActionView(ViewModel.DeleteChatOptionText, _swipeCloseActionViewOptions, pos => + actions.Add(new SimpleSwipeActionView(ViewModel.LocalizedStrings.Delete, _swipeCloseActionViewOptions, pos => { ViewModel.DeleteChatCommand.Execute(ViewModel.Chats[pos]); })); diff --git a/Softeq.XToolkit.Chat.HttpClient/HttpChatAdapter.cs b/Softeq.XToolkit.Chat.HttpClient/HttpChatAdapter.cs index d653b70..a3b0400 100644 --- a/Softeq.XToolkit.Chat.HttpClient/HttpChatAdapter.cs +++ b/Softeq.XToolkit.Chat.HttpClient/HttpChatAdapter.cs @@ -64,12 +64,11 @@ public Task CreateChatAsync(IEnumerable participantsId return _httpClient.GetModelAsync(request, _logger, Mapper.DtoToChatSummary); } - public async Task> GetChatsHeadersAsync() + public async Task> GetChatsListAsync() { var request = new GetChatsListRequest(_chatConfig.ApiUrl); - var result = await _httpClient.GetModelOrExceptionAsync, IList>(request, _logger, - x => x.Select(Mapper.DtoToChatSummary).ToList()) - .ConfigureAwait(false); + var result = await _httpClient.GetModelOrExceptionAsync, IList>( + request, _logger, x => x.Select(Mapper.DtoToChatSummary).ToList()).ConfigureAwait(false); return result.Model; } diff --git a/Softeq.XToolkit.Chat.HttpClient/MockHttpChatAdapter.cs b/Softeq.XToolkit.Chat.HttpClient/MockHttpChatAdapter.cs index d484768..e127d3e 100644 --- a/Softeq.XToolkit.Chat.HttpClient/MockHttpChatAdapter.cs +++ b/Softeq.XToolkit.Chat.HttpClient/MockHttpChatAdapter.cs @@ -87,7 +87,7 @@ public async Task CreateChatAsync(IEnumerable particip }; } - public async Task> GetChatsHeadersAsync() + public async Task> GetChatsListAsync() { return new List { diff --git a/Softeq.XToolkit.Chat.Models/Interfaces/IChatService.cs b/Softeq.XToolkit.Chat.Models/Interfaces/IChatService.cs index f655bf0..20af998 100644 --- a/Softeq.XToolkit.Chat.Models/Interfaces/IChatService.cs +++ b/Softeq.XToolkit.Chat.Models/Interfaces/IChatService.cs @@ -24,7 +24,7 @@ public interface IChatService IObservable ConnectionStatusChanged { get; } SocketConnectionStatus ConnectionStatus { get; } - Task> GetChatsHeadersAsync(); + Task> GetChatsListAsync(); Task CreateChatAsync(string chatName, IList participantsIds, string imagePath); Task CloseChatAsync(string chatId); Task LeaveChatAsync(string chatId); diff --git a/Softeq.XToolkit.Chat.Models/Interfaces/IHttpChatAdapter.cs b/Softeq.XToolkit.Chat.Models/Interfaces/IHttpChatAdapter.cs index ad22cc8..4b9138c 100644 --- a/Softeq.XToolkit.Chat.Models/Interfaces/IHttpChatAdapter.cs +++ b/Softeq.XToolkit.Chat.Models/Interfaces/IHttpChatAdapter.cs @@ -11,7 +11,7 @@ namespace Softeq.XToolkit.Chat.Models.Interfaces public interface IHttpChatAdapter { Task GetUserSummaryAsync(); - Task> GetChatsHeadersAsync(); + Task> GetChatsListAsync(); Task> GetOlderMessagesAsync(string chatId, string messageFromId = null, diff --git a/Softeq.XToolkit.Chat.SignalRClient/SignalRAdapter.cs b/Softeq.XToolkit.Chat.SignalRClient/SignalRAdapter.cs index 90bd8fd..daa3a84 100644 --- a/Softeq.XToolkit.Chat.SignalRClient/SignalRAdapter.cs +++ b/Softeq.XToolkit.Chat.SignalRClient/SignalRAdapter.cs @@ -49,7 +49,6 @@ public SignalRAdapter(IAccountService accountService, IRefreshTokenService refreshTokenService, IRestHttpClient httpClient, ILogManager logManager, - IInternetConnectionManager internetConnectionManager, IChatConfig chatConfig) { _accountService = accountService; @@ -60,8 +59,6 @@ public SignalRAdapter(IAccountService accountService, SubscribeToEvents(); - internetConnectionManager.NetworkConnectionChanged += OnNetworkConnectionChanged; - // TODO YP: need investigate auto-connect (when init before login) //ConnectIfNotConnectedAsync().SafeTaskWrapper(); } @@ -253,19 +250,6 @@ private void SubscribeToEvents() _signalRClient.Disconnected += OnDisconnected; } - private void OnNetworkConnectionChanged(object sender, NetworkConnectionEventArgs e) - { - if (e.IsNetworkAvailable) - { - ConnectIfNotConnectedAsync(true).SafeTaskWrapper(); - } - else - { - _isConnected = false; - UpdateConnectionStatus(SocketConnectionStatus.WaitingForNetwork); - } - } - private async Task CheckConnectionAndSendRequest(TaskReference funcSendRequest) { try diff --git a/Softeq.XToolkit.Chat.SignalRClient/SignalRClient.cs b/Softeq.XToolkit.Chat.SignalRClient/SignalRClient.cs index 954fa34..0a22dac 100644 --- a/Softeq.XToolkit.Chat.SignalRClient/SignalRClient.cs +++ b/Softeq.XToolkit.Chat.SignalRClient/SignalRClient.cs @@ -2,6 +2,7 @@ // http://www.softeq.com using System; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; @@ -10,12 +11,16 @@ using Softeq.XToolkit.Chat.SignalRClient.DTOs.Client; using Softeq.XToolkit.Chat.SignalRClient.DTOs.Member; using Softeq.XToolkit.Chat.SignalRClient.DTOs.Message; +using Softeq.XToolkit.Common.Extensions; namespace Softeq.XToolkit.Chat.SignalRClient { internal class SignalRClient { + private readonly List _subscriptions = new List(); + private HubConnection _connection; + private IDisposable _accessTokenExpiredSubscription; public SignalRClient(string url) { @@ -97,12 +102,16 @@ public async Task ConnectAsync(string accessToken) } } - _connection.On(ClientEvents.AccessTokenExpired, (requestId) => + _accessTokenExpiredSubscription?.Dispose(); + _accessTokenExpiredSubscription = _connection.On(ClientEvents.AccessTokenExpired, (requestId) => { AccessTokenExpired?.Invoke(); }); + var client = await _connection.InvokeAsync(ServerMethods.AddClientAsync).ConfigureAwait(false); + SubscribeToEvents(); + return client; } @@ -241,65 +250,68 @@ public async Task Disconnect() private void SubscribeToEvents() { - _connection.On(ClientEvents.MessageAdded, (message) => + _subscriptions.Apply(x => x.Dispose()); + _subscriptions.Clear(); + + _subscriptions.Add(_connection.On(ClientEvents.MessageAdded, (message) => { MessageAdded?.Invoke(message); - }); + })); - _connection.On(ClientEvents.MessageDeleted, (deletedMessageId, updatedChannelSummary) => + _subscriptions.Add(_connection.On(ClientEvents.MessageDeleted, (deletedMessageId, updatedChannelSummary) => { MessageDeleted?.Invoke(deletedMessageId.ToString(), updatedChannelSummary); - }); + })); - _connection.On(ClientEvents.MessageUpdated, (message) => + _subscriptions.Add(_connection.On(ClientEvents.MessageUpdated, (message) => { MessageUpdated?.Invoke(message); - }); + })); - _connection.On(ClientEvents.MemberLeft, (user, channelId) => + _subscriptions.Add(_connection.On(ClientEvents.MemberLeft, (user, channelId) => { MemberLeft?.Invoke(user, channelId); - }); + })); - _connection.On(ClientEvents.MemberJoined, (user, channel) => + _subscriptions.Add(_connection.On(ClientEvents.MemberJoined, (user, channel) => { MemberJoined?.Invoke(user, channel); - }); + })); - _connection.On(ClientEvents.ChannelUpdated, (channel) => + _subscriptions.Add(_connection.On(ClientEvents.ChannelUpdated, (channel) => { ChannelUpdated?.Invoke(channel); - }); + })); - _connection.On(ClientEvents.ChannelClosed, (channel) => + _subscriptions.Add(_connection.On(ClientEvents.ChannelClosed, (channel) => { ChannelClosed?.Invoke(channel); - }); + })); _connection.On(ClientEvents.ChannelAdded, (channel) => { ChannelAdded?.Invoke(channel); }); - _connection.On(ClientEvents.AttachmentAdded, (username, message) => + _subscriptions.Add(_connection.On(ClientEvents.AttachmentAdded, (username, message) => { AttachmentAdded?.Invoke(username, message); - }); + })); - _connection.On(ClientEvents.AttachmentDeleted, (username, message) => + _subscriptions.Add(_connection.On(ClientEvents.AttachmentDeleted, (username, message) => { AttachmentDeleted?.Invoke(username, message); - }); + })); - _connection.On(ClientEvents.TypingStarted, (username) => + _subscriptions.Add(_connection.On(ClientEvents.TypingStarted, (username) => { TypingStarted?.Invoke(username); - }); + })); - _connection.On(ClientEvents.TypingEnded, (username) => + _subscriptions.Add(_connection.On(ClientEvents.TypingEnded, (username) => { TypingEnded?.Invoke(username); - }); + })); } } } diff --git a/Softeq.XToolkit.Chat.iOS/StyleHelper.cs b/Softeq.XToolkit.Chat.iOS/StyleHelper.cs index 0c9fd01..c7aea8c 100644 --- a/Softeq.XToolkit.Chat.iOS/StyleHelper.cs +++ b/Softeq.XToolkit.Chat.iOS/StyleHelper.cs @@ -17,7 +17,7 @@ internal static class StyleHelper public interface IChatIosStyle { - UIColor NavigationBarTintColor { get; } + UIColor AccentColor { get; } UIColor ButtonTintColor { get; } AvatarImageHelpers.AvatarStyles AvatarStyles { get; } diff --git a/Softeq.XToolkit.Chat.iOS/ViewControllers/AddContactsViewController.cs b/Softeq.XToolkit.Chat.iOS/ViewControllers/AddContactsViewController.cs index 7636a13..d856699 100644 --- a/Softeq.XToolkit.Chat.iOS/ViewControllers/AddContactsViewController.cs +++ b/Softeq.XToolkit.Chat.iOS/ViewControllers/AddContactsViewController.cs @@ -6,6 +6,7 @@ using Softeq.XToolkit.Bindings.iOS; using Softeq.XToolkit.Chat.iOS.Views; using Softeq.XToolkit.Chat.ViewModels; +using Softeq.XToolkit.Common; using Softeq.XToolkit.WhiteLabel.iOS; using Softeq.XToolkit.WhiteLabel.iOS.Extensions; using UIKit; @@ -66,7 +67,7 @@ private void InitNavigationBar() CustomNavigationBarItem.SetCommand( ViewModel.Resources.Done, - StyleHelper.Style.NavigationBarTintColor, + StyleHelper.Style.AccentColor, ViewModel.DoneCommand, false); } @@ -85,10 +86,11 @@ private void InitSearchMembersTableView() TableView.RowHeight = DefaultFoundMembersCellHeight; TableView.RegisterNibForCellReuse(ChatUserViewCell.Nib, ChatUserViewCell.Key); TableView.TableFooterView = new UIView(); + TableView.KeyboardDismissMode = UIScrollViewKeyboardDismissMode.Interactive; var source = new ObservableTableViewSource { - DataSource = ViewModel.FoundContacts, + DataSource = ViewModel.PaginationViewModel.Items, BindCellDelegate = (cell, viewModel, index) => { if (cell is ChatUserViewCell memberCell) @@ -100,6 +102,11 @@ private void InitSearchMembersTableView() }; TableView.Source = source; + + TableView.Delegate = new ContactsTableViewDelegate(new TaskReference(async () => + { + await ViewModel.PaginationViewModel.LoadNextPageAsync(); + })); } private void InitSelectedMembersCollectionView() @@ -127,5 +134,33 @@ private void ResetSelectedRow() TableView.DeselectRow(indexPath, false); } } + + private class ContactsTableViewDelegate : UITableViewDelegate + { + private readonly TaskReference _onScrolled; + + private bool _isBusy; + + public ContactsTableViewDelegate(TaskReference onScrolled) + { + _onScrolled = onScrolled ?? throw new ArgumentNullException(nameof(onScrolled)); + } + + public override async void Scrolled(UIScrollView scrollView) + { + var currentOffset = scrollView.ContentOffset.Y; + var maximumOffset = scrollView.ContentSize.Height - scrollView.Frame.Size.Height; + var deltaOffset = maximumOffset - currentOffset; + + if (deltaOffset < 0 && !_isBusy) + { + _isBusy = true; + + await _onScrolled.RunAsync(); + + _isBusy = false; + } + } + } } } diff --git a/Softeq.XToolkit.Chat.iOS/ViewControllers/ChatMessagesViewController.InputAndKeyboard.cs b/Softeq.XToolkit.Chat.iOS/ViewControllers/ChatMessagesViewController.InputAndKeyboard.cs index 3c82b34..d277325 100644 --- a/Softeq.XToolkit.Chat.iOS/ViewControllers/ChatMessagesViewController.InputAndKeyboard.cs +++ b/Softeq.XToolkit.Chat.iOS/ViewControllers/ChatMessagesViewController.InputAndKeyboard.cs @@ -81,7 +81,6 @@ private void RegisterKeyboardObservers() { return; } - InputBar.SetInputPlaceholderHidden(true); }); _textViewEditingEndedObserver = UITextView.Notifications.ObserveTextDidEndEditing((sender, e) => @@ -90,7 +89,6 @@ private void RegisterKeyboardObservers() { return; } - InputBar.SetInputPlaceholderHidden(!string.IsNullOrEmpty(ViewModel.MessageToSendBody)); }); _textViewContentSizeObserver = InputBar.Input.AddObserver(ContentSizeKey, NSKeyValueObservingOptions.OldNew, e => diff --git a/Softeq.XToolkit.Chat.iOS/ViewControllers/ChatMessagesViewController.cs b/Softeq.XToolkit.Chat.iOS/ViewControllers/ChatMessagesViewController.cs index 79923f1..e67ff54 100644 --- a/Softeq.XToolkit.Chat.iOS/ViewControllers/ChatMessagesViewController.cs +++ b/Softeq.XToolkit.Chat.iOS/ViewControllers/ChatMessagesViewController.cs @@ -74,8 +74,9 @@ public override void ViewDidLoad() InputBar.ViewDidLoad(this); InputBar.SetCommandWithArgs(nameof(InputBar.SendRaised), ViewModel.SendCommand); - + InputBar.SetCommand(nameof(InputBar.PickerWillOpen), new RelayCommand(UnregisterKeyboardObservers)); InputBar.EditingClose.SetCommand(ViewModel.CancelEditingMessageModeCommand); + InputBar.EditingClose.SetImage(UIImage.FromBundle(StyleHelper.Style.CloseButtonImageBoundleName), UIControlState.Normal); ScrollToBottomButton.SetCommand(new RelayCommand(() => ScrollToBottom(true))); ScrollToBottomButton.SetBackgroundImage(UIImage.FromBundle(StyleHelper.Style.ScrollDownBoundleName), UIControlState.Normal); @@ -83,10 +84,11 @@ public override void ViewDidLoad() public override void ViewWillAppear(bool animated) { + RegisterKeyboardObservers(); + base.ViewWillAppear(animated); - ViewModel.MessageAddedCommand = new RelayCommand(OnMessageAdded); - RegisterKeyboardObservers(); + ViewModel.MessageAddedCommand = new RelayCommand(OnMessageAdded); } public override void ViewDidAppear(bool animated) @@ -142,10 +144,14 @@ protected override void DoAttachBindings() InputBar.ChangeEditingMode(ViewModel.IsInEditMessageMode); })); Bindings.Add(this.SetBinding(() => ViewModel.Messages, () => _dataSourceRef.Target.DataSource)); - Bindings.Add(this.SetBinding(() => ViewModel.ConnectionStatusViewModel).WhenSourceChanges(() => + Bindings.Add(this.SetBinding(() => ViewModel.ConnectionStatusViewModel.ConnectionStatusText).WhenSourceChanges(() => { _customTitleView.Update(ViewModel.ConnectionStatusViewModel); })); + Bindings.Add(this.SetBinding(() => ViewModel.MessageToSendBody).WhenSourceChanges(() => + { + InputBar.SetTextPlaceholdervisibility(string.IsNullOrEmpty(ViewModel.MessageToSendBody)); + })); } public override void ObserveValue(NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context) diff --git a/Softeq.XToolkit.Chat.iOS/ViewControllers/ChatsListViewController.cs b/Softeq.XToolkit.Chat.iOS/ViewControllers/ChatsListViewController.cs index 8ebacfe..737dca2 100644 --- a/Softeq.XToolkit.Chat.iOS/ViewControllers/ChatsListViewController.cs +++ b/Softeq.XToolkit.Chat.iOS/ViewControllers/ChatsListViewController.cs @@ -28,12 +28,9 @@ public override void ViewDidLoad() { base.ViewDidLoad(); - // Setup brand color for NavigationBar buttons - CustomNavigationBar.TintColor = StyleHelper.Style.NavigationBarTintColor; - + _customTitleView = new ConnectionStatusView(CGRect.Empty); - - // Setup NavigationBar + CustomNavigationBar.TintColor = StyleHelper.Style.AccentColor; CustomNavigationItem.AddTitleView(_customTitleView); CustomNavigationItem.SetCommand(UIBarButtonSystemItem.Add, ViewModel.CreateChatCommand, false); @@ -56,7 +53,7 @@ protected override void DoAttachBindings() Bindings.Add(this.SetBinding(() => ViewModel.SelectedChat, () => _sourceRef.Target.SelectedItem, BindingMode.TwoWay)); - Bindings.Add(this.SetBinding(() => ViewModel.ConnectionStatusViewModel).WhenSourceChanges(() => + Bindings.Add(this.SetBinding(() => ViewModel.ConnectionStatusViewModel.ConnectionStatusText).WhenSourceChanges(() => { _customTitleView.Update(ViewModel.ConnectionStatusViewModel); })); @@ -96,7 +93,7 @@ public override UITableViewRowAction[] EditActionsForRow(UITableView tableView, { UITableViewRowAction.Create( UITableViewRowActionStyle.Default, - _viewModelRef.Target?.LeaveChatOptionText, + _viewModelRef.Target?.LocalizedStrings.Leave, (row, index) => OnClickLeave(row, index, tableView)) }; @@ -104,7 +101,7 @@ public override UITableViewRowAction[] EditActionsForRow(UITableView tableView, { var closeButton = UITableViewRowAction.Create( UITableViewRowActionStyle.Default, - _viewModelRef.Target?.DeleteChatOptionText, + _viewModelRef.Target?.LocalizedStrings.Delete, (row, index) => OnClickDelete(row, index, tableView)); closeButton.BackgroundColor = UIColor.Orange; buttons.Add(closeButton); diff --git a/Softeq.XToolkit.Chat.iOS/ViewControllers/SelectContactsViewController.cs b/Softeq.XToolkit.Chat.iOS/ViewControllers/SelectContactsViewController.cs index b249310..19852a6 100644 --- a/Softeq.XToolkit.Chat.iOS/ViewControllers/SelectContactsViewController.cs +++ b/Softeq.XToolkit.Chat.iOS/ViewControllers/SelectContactsViewController.cs @@ -35,7 +35,7 @@ public override void ViewDidLoad() true); CustomNavigationItem.SetCommand( ViewModel.ActionButtonName, - StyleHelper.Style.NavigationBarTintColor, + StyleHelper.Style.AccentColor, new RelayCommand(AddChat), false); diff --git a/Softeq.XToolkit.Chat.iOS/Views/ChatDetailsHeaderView.cs b/Softeq.XToolkit.Chat.iOS/Views/ChatDetailsHeaderView.cs index c171488..c2189e2 100644 --- a/Softeq.XToolkit.Chat.iOS/Views/ChatDetailsHeaderView.cs +++ b/Softeq.XToolkit.Chat.iOS/Views/ChatDetailsHeaderView.cs @@ -89,6 +89,9 @@ protected override void Initialize() ChatAvatarcontainer.ClipsToBounds = true; EditedChatAvatarImageView.Hidden = true; + + ChangeChatPhotoButton.SetTitleColor(StyleHelper.Style.AccentColor, UIControlState.Normal); + AddMembersButton.SetTitleColor(StyleHelper.Style.AccentColor, UIControlState.Normal); } } } \ No newline at end of file diff --git a/Softeq.XToolkit.Chat.iOS/Views/ChatMessageNode.cs b/Softeq.XToolkit.Chat.iOS/Views/ChatMessageNode.cs index da8761c..04c5d63 100644 --- a/Softeq.XToolkit.Chat.iOS/Views/ChatMessageNode.cs +++ b/Softeq.XToolkit.Chat.iOS/Views/ChatMessageNode.cs @@ -66,14 +66,19 @@ public ChatMessageNode(ChatMessageViewModel viewModel, ContextMenuComponent cont var size = new CGSize(AvatarSize, AvatarSize); return image.MakeCircularImageWithSize(size); }; - _avatarImageNode.ContentMode = UIViewContentMode.ScaleAspectFit; + _avatarImageNode.ContentMode = UIViewContentMode.ScaleAspectFill; _avatarImageNode.Hidden = _isMyMessage; if (!_isMyMessage) { _avatarImageNode.LoadImageWithTextPlaceholder( _viewModelRef.Target.SenderPhotoUrl, _viewModelRef.Target.SenderName, - StyleHelper.Style.AvatarStyles); + new AvatarImageHelpers.AvatarStyles + { + BackgroundHexColors = StyleHelper.Style.AvatarStyles.BackgroundHexColors, + Font = StyleHelper.Style.AvatarStyles.Font, + Size = new System.Drawing.Size((int)AvatarSize, (int)AvatarSize) + }); } _attachmentImageNode.ContentMode = UIViewContentMode.ScaleAspectFit; diff --git a/Softeq.XToolkit.Chat.iOS/Views/ChatMessagesInputBarView.cs b/Softeq.XToolkit.Chat.iOS/Views/ChatMessagesInputBarView.cs index 9908eb0..a14761e 100644 --- a/Softeq.XToolkit.Chat.iOS/Views/ChatMessagesInputBarView.cs +++ b/Softeq.XToolkit.Chat.iOS/Views/ChatMessagesInputBarView.cs @@ -30,6 +30,7 @@ public ChatMessagesInputBarView(CGRect frame) : base(frame) { } public event EventHandler TopContainersHeightChanged; public event EventHandler, string)>>> SendRaised; + public event EventHandler PickerWillOpen; public override CGSize IntrinsicContentSize => _cachedIntrinsicContentSize; @@ -62,11 +63,6 @@ public void UpdateInputHeight(CGSize newSize) InvalidateIntrinsicContentSize(); } - public void SetInputPlaceholderHidden(bool isHidden) - { - InputTextViewPlaceholder.Hidden = isHidden; - } - public void StartEditing(string originalMessageBody) { EditingText.Text = originalMessageBody; @@ -78,7 +74,6 @@ public void ChangeEditingMode(bool isInEditMessageMode) var isEditViewContainerHidden = !isInEditMessageMode; CollapseEditViewContainer(isEditViewContainerHidden); - SetInputPlaceholderHidden(isInEditMessageMode); InvokeTopContainersHeightChangedIfNeeded(); InvalidateIntrinsicContentSize(); @@ -95,13 +90,13 @@ public override void InvalidateIntrinsicContentSize() _cachedIntrinsicContentSize = CalculateIntrinsicContentSize(); } - + public void ViewDidLoad(UIViewController viewController) { SendButton.TintColor = StyleHelper.Style.ButtonTintColor; SendButton.SetImage(UIImage.FromBundle(StyleHelper.Style.SendBundleName), UIControlState.Normal); SendButton.SetCommand(new RelayCommand(OnRaiseSend)); - + AttachImageButton.TintColor = StyleHelper.Style.ButtonTintColor; AttachImageButton.SetImage(UIImage.FromBundle(StyleHelper.Style.AddImageBundleName), UIControlState.Normal); AttachImageButton.SetCommand(new RelayCommand(OnAddPhotoClicked)); @@ -122,7 +117,7 @@ public void ViewDidLoad(UIViewController viewController) EditImageContainerHeightConstraint.Constant = 0; _simpleImagePicker = new SimpleImagePicker(viewController, Dependencies.IocContainer.Resolve(), false); - _attachedImageBinding = this.SetBinding(() => _simpleImagePicker.ViewModel.ImageCacheKey).WhenSourceChanges(() => + _attachedImageBinding = this.SetBinding(() => _simpleImagePicker.ViewModel.ImageCacheKey).WhenSourceChanges(() => { if (string.IsNullOrEmpty(_simpleImagePicker.ViewModel.ImageCacheKey)) { @@ -132,6 +127,12 @@ public void ViewDidLoad(UIViewController viewController) OpenAttachPanel(); }); + _simpleImagePicker.SetCommand(nameof(_simpleImagePicker.PickerWillOpen), new RelayCommand(RaisePickerWillOpen)); + } + + public void SetTextPlaceholdervisibility(bool isVisible) + { + InputTextViewPlaceholder.Hidden = !isVisible; } protected override void Initialize() @@ -140,6 +141,9 @@ protected override void Initialize() AutoresizingMask = UIViewAutoresizing.FlexibleHeight; EditingIndicatorView.Layer.CornerRadius = 2f; + EditingIndicatorView.BackgroundColor = StyleHelper.Style.AccentColor; + + EditMessageHeaderLabel.TextColor = StyleHelper.Style.AccentColor; _editViewContainerInitialHeight = EditViewContainerHeightConstraint.Constant; UpdateEditViewHeightConstraint(); @@ -173,7 +177,7 @@ private void UpdateEditViewHeightConstraint() private void InvokeTopContainersHeightChangedIfNeeded() { - var changedHeight = EditViewContainerHeightConstraint.Constant + var changedHeight = EditViewContainerHeightConstraint.Constant + EditImageContainerHeightConstraint.Constant; if (changedHeight == _cachedHeight) @@ -212,7 +216,7 @@ private void OnRemovePhotoClicked() private void OpenAttachPanel() { - Execute.BeginOnUIThread(() => + Execute.BeginOnUIThread(() => { var key = _simpleImagePicker.ViewModel.ImageCacheKey; if (key == _previewImageKey) @@ -237,9 +241,10 @@ private void OpenAttachPanel() private void CloseAttachPanel() { - Execute.BeginOnUIThread(() => + Execute.BeginOnUIThread(() => { _previewImageKey = null; + _simpleImagePicker.ViewModel.ImageCacheKey = null; AttachedImageView.Image?.Dispose(); AttachedImageView.Image = null; EditImageContainer.Hidden = true; @@ -255,5 +260,10 @@ private void OnRaiseSend() SendRaised?.Invoke(this, new GenericEventArgs, string)>>(parameter)); CloseAttachPanel(); } + + private void RaisePickerWillOpen() + { + PickerWillOpen?.Invoke(this, EventArgs.Empty); + } } } \ No newline at end of file diff --git a/Softeq.XToolkit.Chat.iOS/Views/ChatMessagesInputBarView.designer.cs b/Softeq.XToolkit.Chat.iOS/Views/ChatMessagesInputBarView.designer.cs index 284752b..7712766 100644 --- a/Softeq.XToolkit.Chat.iOS/Views/ChatMessagesInputBarView.designer.cs +++ b/Softeq.XToolkit.Chat.iOS/Views/ChatMessagesInputBarView.designer.cs @@ -33,6 +33,9 @@ partial class ChatMessagesInputBarView [Outlet] UIKit.UILabel EditingText { get; set; } + [Outlet] + UIKit.UILabel EditMessageHeaderLabel { get; set; } + [Outlet] UIKit.UIView EditViewContainer { get; set; } @@ -141,6 +144,11 @@ void ReleaseDesignerOutlets () TakePhotoButton.Dispose (); TakePhotoButton = null; } + + if (EditMessageHeaderLabel != null) { + EditMessageHeaderLabel.Dispose (); + EditMessageHeaderLabel = null; + } } } } diff --git a/Softeq.XToolkit.Chat.iOS/Views/ChatMessagesInputBarView.xib b/Softeq.XToolkit.Chat.iOS/Views/ChatMessagesInputBarView.xib index d372e8b..c2a2178 100644 --- a/Softeq.XToolkit.Chat.iOS/Views/ChatMessagesInputBarView.xib +++ b/Softeq.XToolkit.Chat.iOS/Views/ChatMessagesInputBarView.xib @@ -16,6 +16,7 @@ + @@ -87,7 +88,6 @@ - @@ -208,7 +208,4 @@ - - - diff --git a/Softeq.XToolkit.Chat.iOS/Views/ChatSummaryViewCell.cs b/Softeq.XToolkit.Chat.iOS/Views/ChatSummaryViewCell.cs index 8d018cb..69581cd 100644 --- a/Softeq.XToolkit.Chat.iOS/Views/ChatSummaryViewCell.cs +++ b/Softeq.XToolkit.Chat.iOS/Views/ChatSummaryViewCell.cs @@ -73,7 +73,7 @@ public void BindViewModel(ChatSummaryViewModel viewModel) { UnreadMessageCountBackground.BackgroundColor = _viewModelRef.Target.IsMuted ? UIColor.FromRGB(180, 180, 180) - : UIColor.FromRGB(91, 198, 201); + : StyleHelper.Style.AccentColor; } })); _bindings.Add(this.SetBinding(() => _viewModelRef.Target.UnreadMessageCount).WhenSourceChanges(() => diff --git a/Softeq.XToolkit.Chat.iOS/Views/ChatUserViewCell.cs b/Softeq.XToolkit.Chat.iOS/Views/ChatUserViewCell.cs index d0428cc..6bc3f07 100644 --- a/Softeq.XToolkit.Chat.iOS/Views/ChatUserViewCell.cs +++ b/Softeq.XToolkit.Chat.iOS/Views/ChatUserViewCell.cs @@ -22,6 +22,7 @@ public partial class ChatUserViewCell : UITableViewCell private readonly List _bindings = new List(); private WeakReferenceEx _viewModelRef; + private UITapGestureRecognizer _tapGesture; static ChatUserViewCell() { @@ -51,6 +52,27 @@ public void BindViewModel(ChatUserViewModel viewModel) _bindings.Add(this.SetBinding(() => _viewModelRef.Target.IsSelected, () => CheckBoxButton.Selected, BindingMode.TwoWay)); _bindings.Add(this.SetBinding(() => _viewModelRef.Target.IsSelectable, () => CheckBoxButton.Hidden) .ConvertSourceToTarget(x => !x)); + + _tapGesture = new UITapGestureRecognizer(() => + { + _viewModelRef.Target.IsSelected = !_viewModelRef.Target.IsSelected; + }) + { + NumberOfTapsRequired = 1 + }; + + UserInteractionEnabled = true; + AddGestureRecognizer(_tapGesture); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + RemoveGestureRecognizer(_tapGesture); + } + + base.Dispose(disposing); } } } diff --git a/Softeq.XToolkit.Chat.iOS/Views/SelectedMemberViewCell.cs b/Softeq.XToolkit.Chat.iOS/Views/SelectedMemberViewCell.cs index 4cdf6dc..afd7c23 100644 --- a/Softeq.XToolkit.Chat.iOS/Views/SelectedMemberViewCell.cs +++ b/Softeq.XToolkit.Chat.iOS/Views/SelectedMemberViewCell.cs @@ -17,6 +17,7 @@ public partial class SelectedMemberViewCell : UICollectionViewCell public static readonly UINib Nib; private ChatUserViewModel _memberViewModel; + private UITapGestureRecognizer _imageTapGesture; protected SelectedMemberViewCell(IntPtr handle) : base(handle) { } @@ -30,10 +31,7 @@ public void BindCell(ChatUserViewModel memberViewModel) _memberViewModel = memberViewModel; RemoveMemberBtn.SetBackgroundImage(UIImage.FromBundle(StyleHelper.Style.RemoveAttachBundleName), UIControlState.Normal); - RemoveMemberBtn.SetCommand(new RelayCommand(() => - { - _memberViewModel.IsSelected = false; - })); + RemoveMemberBtn.SetCommand(new RelayCommand(RemoveSelection)); MemberNameLabel.Text = _memberViewModel.Username; @@ -42,6 +40,29 @@ public void BindCell(ChatUserViewModel memberViewModel) _memberViewModel.Username, StyleHelper.Style.AvatarStyles, x => x.Transform(new CircleTransformation())); + + _imageTapGesture = new UITapGestureRecognizer(RemoveSelection) + { + NumberOfTapsRequired = 1 + }; + + MemberPhotoImageView.UserInteractionEnabled = true; + MemberPhotoImageView.AddGestureRecognizer(_imageTapGesture); + } + + private void RemoveSelection() + { + _memberViewModel.IsSelected = false; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + MemberPhotoImageView.RemoveGestureRecognizer(_imageTapGesture); + } + + base.Dispose(disposing); } } } diff --git a/Softeq.XToolkit.Chat.iOS/Views/SelectedMemberViewCell.designer.cs b/Softeq.XToolkit.Chat.iOS/Views/SelectedMemberViewCell.designer.cs index 2144e63..40413d2 100644 --- a/Softeq.XToolkit.Chat.iOS/Views/SelectedMemberViewCell.designer.cs +++ b/Softeq.XToolkit.Chat.iOS/Views/SelectedMemberViewCell.designer.cs @@ -9,34 +9,37 @@ namespace Softeq.XToolkit.Chat.iOS.Views { - [Register ("SelectedMemberViewCell")] - partial class SelectedMemberViewCell - { - [Outlet] - UIKit.UILabel MemberNameLabel { get; set; } + [Register("SelectedMemberViewCell")] + partial class SelectedMemberViewCell + { + [Outlet] + UIKit.UILabel MemberNameLabel { get; set; } - [Outlet] - UIKit.UIImageView MemberPhotoImageView { get; set; } + [Outlet] + UIKit.UIImageView MemberPhotoImageView { get; set; } - [Outlet] - UIKit.UIButton RemoveMemberBtn { get; set; } - - void ReleaseDesignerOutlets () - { - if (MemberPhotoImageView != null) { - MemberPhotoImageView.Dispose (); - MemberPhotoImageView = null; - } + [Outlet] + UIKit.UIButton RemoveMemberBtn { get; set; } - if (RemoveMemberBtn != null) { - RemoveMemberBtn.Dispose (); - RemoveMemberBtn = null; - } + void ReleaseDesignerOutlets() + { + if (MemberNameLabel != null) + { + MemberNameLabel.Dispose(); + MemberNameLabel = null; + } - if (MemberNameLabel != null) { - MemberNameLabel.Dispose (); - MemberNameLabel = null; - } - } - } + if (MemberPhotoImageView != null) + { + MemberPhotoImageView.Dispose(); + MemberPhotoImageView = null; + } + + if (RemoveMemberBtn != null) + { + RemoveMemberBtn.Dispose(); + RemoveMemberBtn = null; + } + } + } } diff --git a/Softeq.XToolkit.Chat.iOS/Views/SelectedMemberViewCell.xib b/Softeq.XToolkit.Chat.iOS/Views/SelectedMemberViewCell.xib index 6849dc8..34964b0 100644 --- a/Softeq.XToolkit.Chat.iOS/Views/SelectedMemberViewCell.xib +++ b/Softeq.XToolkit.Chat.iOS/Views/SelectedMemberViewCell.xib @@ -21,8 +21,9 @@ - + + diff --git a/Softeq.XToolkit.Chat/Bootstrapper.cs b/Softeq.XToolkit.Chat/Bootstrapper.cs index 0a2ac7e..5f81a02 100644 --- a/Softeq.XToolkit.Chat/Bootstrapper.cs +++ b/Softeq.XToolkit.Chat/Bootstrapper.cs @@ -3,6 +3,7 @@ using Autofac; using Softeq.XToolkit.Chat.HttpClient; +using Softeq.XToolkit.Chat.Interfaces; using Softeq.XToolkit.Chat.Manager; using Softeq.XToolkit.Chat.Models.Interfaces; using Softeq.XToolkit.Chat.SignalRClient; @@ -21,7 +22,11 @@ public static void Configure(ContainerBuilder containerBuilder) .InstancePerLifetimeScope(); containerBuilder.RegisterType().As() .InstancePerLifetimeScope(); - containerBuilder.RegisterType().InstancePerLifetimeScope(); + containerBuilder.RegisterType() + .As() + .As() + .As() + .InstancePerLifetimeScope(); containerBuilder.RegisterType().As().InstancePerLifetimeScope(); containerBuilder.RegisterType().As().InstancePerLifetimeScope(); containerBuilder.RegisterType().As().InstancePerLifetimeScope(); diff --git a/Softeq.XToolkit.Chat/ChatService.cs b/Softeq.XToolkit.Chat/ChatService.cs index c70195c..a3a1ed6 100644 --- a/Softeq.XToolkit.Chat/ChatService.cs +++ b/Softeq.XToolkit.Chat/ChatService.cs @@ -76,22 +76,24 @@ public Task InviteMembersAsync(string chatId, IList participantsIds) return _socketChatAdapter.InviteMembersAsync(chatId, participantsIds); } - public virtual async Task> GetChatsHeadersAsync() + public virtual async Task> GetChatsListAsync() { - var chats = await _httpChatAdapter.GetChatsHeadersAsync().ConfigureAwait(false); - if (chats != null) + var models = await _httpChatAdapter.GetChatsListAsync().ConfigureAwait(false); + if (models == null) { - var userId = await GetUserIdAsync().ConfigureAwait(false); - chats.Apply(x => x.UpdateIsCreatedByMeStatus(userId)); - return chats.ToList(); + return null; } - return null; + + var userId = await GetUserIdAsync().ConfigureAwait(false); + models.Apply(x => x.UpdateIsCreatedByMeStatus(userId)); + + return models.ToList(); } public virtual async Task> GetOlderMessagesAsync(string chatId, - string messageFromId = null, - DateTimeOffset? messageFromDateTime = null, - int? count = null) + string messageFromId = null, + DateTimeOffset? messageFromDateTime = null, + int? count = null) { var messages = await _httpChatAdapter.GetOlderMessagesAsync(chatId, messageFromId, messageFromDateTime, count) .ConfigureAwait(false); @@ -188,11 +190,12 @@ private async Task GetUserIdAsync() await _semaphoreSlim.WaitAsync().ConfigureAwait(false); if (string.IsNullOrEmpty(_cachedUserId)) { - var userSummary = default(ChatUserModel); + ChatUserModel userSummary; do { userSummary = await _httpChatAdapter.GetUserSummaryAsync().ConfigureAwait(false); - } while (string.IsNullOrEmpty(userSummary?.SaasUserId)); + } + while (string.IsNullOrEmpty(userSummary?.SaasUserId)); _cachedUserId = userSummary.SaasUserId; } result = _cachedUserId; diff --git a/Softeq.XToolkit.Chat/InMemoryMessagesCache.cs b/Softeq.XToolkit.Chat/InMemoryMessagesCache.cs index 68f196c..aa92862 100644 --- a/Softeq.XToolkit.Chat/InMemoryMessagesCache.cs +++ b/Softeq.XToolkit.Chat/InMemoryMessagesCache.cs @@ -110,9 +110,16 @@ public void TryDeleteMessage(string chatId, string deletedMessageId) public ChatMessageModel FindDuplicateMessage(ChatMessageModel message) { var chatMessages = GetMessagesCollectionForChat(message.ChannelId); - return chatMessages.FirstOrDefault(x => (x.Id == null || x.Id == message.Id) && - x.Body == message.Body && - x.IsMine == message.IsMine); + ChatMessageModel chatMessageModel = null; + + lock (chatMessages) + { + chatMessageModel = chatMessages.FirstOrDefault(x => (x.Id == null || x.Id == message.Id) && + x.Body == message.Body && + x.IsMine == message.IsMine); + } + + return chatMessageModel; } public void UpdateSentMessage(ChatMessageModel sentMessage, ChatMessageModel deliveredMessage) diff --git a/Softeq.XToolkit.Chat/Interfaces/IChatConnectionManager.cs b/Softeq.XToolkit.Chat/Interfaces/IChatConnectionManager.cs new file mode 100644 index 0000000..5172428 --- /dev/null +++ b/Softeq.XToolkit.Chat/Interfaces/IChatConnectionManager.cs @@ -0,0 +1,11 @@ +using System; +using Softeq.XToolkit.Chat.Models.Enum; + +namespace Softeq.XToolkit.Chat.Interfaces +{ + public interface IChatConnectionManager + { + IObservable ConnectionStatusChanged { get; } + ConnectionStatus ConnectionStatus { get; } + } +} \ No newline at end of file diff --git a/Softeq.XToolkit.Chat/Interfaces/IChatsListManager.cs b/Softeq.XToolkit.Chat/Interfaces/IChatsListManager.cs new file mode 100644 index 0000000..5210fff --- /dev/null +++ b/Softeq.XToolkit.Chat/Interfaces/IChatsListManager.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Softeq.XToolkit.Chat.Models; +using Softeq.XToolkit.Chat.ViewModels; +using Softeq.XToolkit.Common.Collections; + +namespace Softeq.XToolkit.Chat.Interfaces +{ + public interface IChatsListManager + { + ObservableRangeCollection ChatsCollection { get; } + + Task CreateChatAsync(string chatName, IList participantsIds, string imagePath); + Task EditChatAsync(ChatSummaryModel chatSummary); + Task CloseChatAsync(string chatId); + Task LeaveChatAsync(string chatId); + + Task InviteMembersAsync(string chatId, IList participantsIds); + Task> GetChatMembersAsync(string chatId); + + void RefreshChatsListOnBackgroundAsync(); + } +} \ No newline at end of file diff --git a/Softeq.XToolkit.Chat/Manager/ChatManager.Chats.cs b/Softeq.XToolkit.Chat/Manager/ChatManager.Chats.cs index d2d0e83..cd17818 100644 --- a/Softeq.XToolkit.Chat/Manager/ChatManager.Chats.cs +++ b/Softeq.XToolkit.Chat/Manager/ChatManager.Chats.cs @@ -5,34 +5,65 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Softeq.XToolkit.Chat.Interfaces; using Softeq.XToolkit.Chat.Models; using Softeq.XToolkit.Chat.ViewModels; using Softeq.XToolkit.Common.Collections; -using TaskExtensions = Softeq.XToolkit.Common.Extensions.TaskExtensions; +using Softeq.XToolkit.Common.Extensions; namespace Softeq.XToolkit.Chat.Manager { - public partial class ChatManager + public partial class ChatManager : IChatsListManager { - public async Task UpdateChatsListAsync() + public void MakeChatActive(string chatId) { - var result = await GetChatsListAsync(); + _activeChatId = chatId; + } + + private Task UpdateChatsListFromNetworkAsync() + { + return UpdateChatsListWithLoader(GetChatsListAsync()); + } + + private async Task UpdateChatsListWithLoader(Task> loader) + { + var result = await loader.ConfigureAwait(false); if (result != null) { - ModifyChatsSafely(() => ChatsCollection.ReplaceRange(result.OrderByDescending(x => x.LastUpdateDate))); + var orderedChats = result + .Select(_viewModelFactoryService.ResolveViewModel) + .OrderByDescending(x => x.LastUpdateDate) + .ToList(); + + ModifyChatsSafely(() => + { + ChatsCollection.ReplaceRange(orderedChats); + }); } } - public void MakeChatActive(string chatId) + private async Task> GetChatsListAsync() { - _activeChatId = chatId; + var models = await _chatService.GetChatsListAsync().ConfigureAwait(false); + if (models == null) + { + return null; + } + + await _localCache.Add(ChatsCacheKey, DateTimeOffset.UtcNow, models).ConfigureAwait(false); + + return models; } - public async Task> GetChatsListAsync() + public void RefreshChatsListOnBackgroundAsync() { - var models = await _chatService.GetChatsHeadersAsync().ConfigureAwait(false); - return models?.Select(_viewModelFactoryService.ResolveViewModel)?.ToList(); + if (ChatsCollection.Count == 0) + { + UpdateChatsListWithLoader(_localCache.Get>(ChatsCacheKey)).SafeTaskWrapper(); + } + + UpdateChatsListFromNetworkAsync().SafeTaskWrapper(); } public async Task CreateChatAsync(string chatName, IList participantsIds, string imagePath) @@ -59,26 +90,13 @@ public Task InviteMembersAsync(string chatId, IList participantsIds) return _chatService.InviteMembersAsync(chatId, participantsIds); } - public async Task> GetContactsAsync(string nameFilter, int pageNumber, int pageSize) - { - var models = await _chatService.GetContactsAsync(nameFilter, pageNumber, pageSize).ConfigureAwait(false); - if (models == null) - { - return new List(); - } - - return models.Data - .Select(_viewModelFactoryService.ResolveViewModel) - .ToList(); - } - public async Task> GetChatMembersAsync(string chatId) { var models = await _chatService.GetChatMembersAsync(chatId).ConfigureAwait(false); - return models?.Select(_viewModelFactoryService.ResolveViewModel)?.ToList(); + return models?.Select(_viewModelFactoryService.ResolveViewModel).ToList(); } - internal Task EditChatAsync(ChatSummaryModel chatSummary) + public Task EditChatAsync(ChatSummaryModel chatSummary) { return _chatService.EditChatAsync(chatSummary); } @@ -109,7 +127,7 @@ private void OnChatRemoved(string chatId) } }); - TaskExtensions.SafeTaskWrapper(_messagesCache.RemoveMessagesAsync(chatId)); + _messagesCache.RemoveMessagesAsync(chatId).SafeTaskWrapper(); } private void ModifyChatsSafely(Action modifyAction) diff --git a/Softeq.XToolkit.Chat/Manager/ChatManager.Messages.cs b/Softeq.XToolkit.Chat/Manager/ChatManager.Messages.cs index 3d5a292..a31e08e 100644 --- a/Softeq.XToolkit.Chat/Manager/ChatManager.Messages.cs +++ b/Softeq.XToolkit.Chat/Manager/ChatManager.Messages.cs @@ -179,7 +179,7 @@ private ChatMessageViewModel AddLatestMessage(ChatMessageModel messageModel) if (TryAddChat(new ChatSummaryModel { Id = messageViewModel.ChatId })) { - UpdateChatsListAsync(); + UpdateChatsListFromNetworkAsync(); } ModifyChatsSafely(() => { diff --git a/Softeq.XToolkit.Chat/Manager/ChatManager.cs b/Softeq.XToolkit.Chat/Manager/ChatManager.cs index b995104..6316d13 100644 --- a/Softeq.XToolkit.Chat/Manager/ChatManager.cs +++ b/Softeq.XToolkit.Chat/Manager/ChatManager.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using System.Reactive.Subjects; using System.Threading.Tasks; +using Softeq.XToolkit.Chat.Interfaces; using Softeq.XToolkit.Chat.Models; using Softeq.XToolkit.Chat.Models.Enum; using Softeq.XToolkit.Chat.Models.Interfaces; @@ -18,15 +19,18 @@ namespace Softeq.XToolkit.Chat.Manager { - public partial class ChatManager + public partial class ChatManager : IChatConnectionManager { + private const string ChatsCacheKey = "chat_chats"; + private readonly IChatService _chatService; private readonly IMessagesCache _messagesCache; private readonly IViewModelFactoryService _viewModelFactoryService; private readonly IUploadImageService _uploadImageService; + private readonly ILocalCache _localCache; private readonly ILogger _logger; - private readonly List _subscriptions = new List(); + private readonly IList _subscriptions = new List(); private readonly Subject _messageAdded = new Subject(); private readonly Subject _messageEdited = new Subject(); @@ -44,12 +48,14 @@ public ChatManager( IMessagesCache messagesCache, IViewModelFactoryService viewModelFactoryService, ILogManager logManager, - IUploadImageService uploadImageService) + IUploadImageService uploadImageService, + ILocalCache localCache) { _chatService = chatService; _messagesCache = messagesCache; _viewModelFactoryService = viewModelFactoryService; _uploadImageService = uploadImageService; + _localCache = localCache; _logger = logManager.GetLogger(); _messagesCache.Init(new Common.TaskReference>( @@ -68,7 +74,7 @@ public ChatManager( _subscriptions.Add(_chatService.ConnectionStatusChanged.Subscribe(OnConnectionStatusChanged)); OnConnectionStatusChanged(_chatService.ConnectionStatus); - Messenger.Default.Register(this, x => ForceReconnect()); + Messenger.Default.Register(this, x => _chatService.ForceReconnect()); Messenger.Default.Register(this, x => _chatService.ForceDisconnect()); } @@ -93,11 +99,6 @@ private set public ObservableRangeCollection ChatsCollection { get; } = new ObservableRangeCollection(); - public void ForceReconnect() - { - _chatService.ForceReconnect(); - } - public void Logout() { _chatService.Logout(); @@ -128,10 +129,10 @@ private void OnConnectionStatusChanged(SocketConnectionStatus connectionStatus) private async Task UpdateCacheAsync() { ConnectionStatus = ConnectionStatus.Updating; - await Task.WhenAll(UpdateChatsListAsync(), UpdateMessagesCacheAsync()).ConfigureAwait(false); + await Task.WhenAll(UpdateChatsListFromNetworkAsync(), UpdateMessagesCacheAsync()).ConfigureAwait(false); ConnectionStatus = ConnectionStatus.Online; } - + private void OnCacheUpdated( string chatId, IList addedMessages, @@ -142,11 +143,23 @@ private void OnCacheUpdated( { return; } + var addedMessagesViewModels = CreateMessagesViewModels(addedMessages); - _messagesBatchAdded.OnNext(addedMessagesViewModels); - _messagesBatchUpdated.OnNext(updatedMessages); - _messagesBatchDeleted.OnNext(deletedMessagesIds); + if (addedMessagesViewModels.Count > 0) + { + _messagesBatchAdded.OnNext(addedMessagesViewModels); + } + + if (updatedMessages.Count > 0) + { + _messagesBatchUpdated.OnNext(updatedMessages); + } + + if (deletedMessagesIds.Count > 0) + { + _messagesBatchDeleted.OnNext(deletedMessagesIds); + } } } } diff --git a/Softeq.XToolkit.Chat/MockChatService.cs b/Softeq.XToolkit.Chat/MockChatService.cs index 732fd75..117b183 100644 --- a/Softeq.XToolkit.Chat/MockChatService.cs +++ b/Softeq.XToolkit.Chat/MockChatService.cs @@ -18,7 +18,7 @@ public MockChatService(ISocketChatAdapter socketChatAdapter, { } - public override async Task> GetChatsHeadersAsync() + public override async Task> GetChatsListAsync() { await Task.Delay(500); return new List { new ChatSummaryModel { Id = "1", Name = "test" } }; diff --git a/Softeq.XToolkit.Chat/Strategies/Search/CreateChatSearchContactsStrategy.cs b/Softeq.XToolkit.Chat/Strategies/Search/CreateChatSearchContactsStrategy.cs index 1125973..6aa9423 100644 --- a/Softeq.XToolkit.Chat/Strategies/Search/CreateChatSearchContactsStrategy.cs +++ b/Softeq.XToolkit.Chat/Strategies/Search/CreateChatSearchContactsStrategy.cs @@ -1,25 +1,25 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using System.Collections.Generic; using System.Threading.Tasks; -using Softeq.XToolkit.Chat.Manager; -using Softeq.XToolkit.Chat.ViewModels; +using Softeq.XToolkit.Chat.Models; +using Softeq.XToolkit.Chat.Models.Interfaces; +using Softeq.XToolkit.Common.Models; namespace Softeq.XToolkit.Chat.Strategies.Search { public class CreateChatSearchContactsStrategy : ISearchContactsStrategy { - private readonly ChatManager _chatManager; + private readonly IChatService _chatService; - public CreateChatSearchContactsStrategy(ChatManager chatManager) + public CreateChatSearchContactsStrategy(IChatService chatService) { - _chatManager = chatManager; + _chatService = chatService; } - public Task> Search(string query, int pageNumber, int pageSize) + public Task> Search(string query, int pageNumber, int pageSize) { - return _chatManager.GetContactsAsync(query, pageNumber, pageSize); + return _chatService.GetContactsAsync(query, pageNumber, pageSize); } } } \ No newline at end of file diff --git a/Softeq.XToolkit.Chat/Strategies/Search/ISearchContactsStrategy.cs b/Softeq.XToolkit.Chat/Strategies/Search/ISearchContactsStrategy.cs index f87199d..49764e6 100644 --- a/Softeq.XToolkit.Chat/Strategies/Search/ISearchContactsStrategy.cs +++ b/Softeq.XToolkit.Chat/Strategies/Search/ISearchContactsStrategy.cs @@ -1,14 +1,14 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using System.Collections.Generic; using System.Threading.Tasks; -using Softeq.XToolkit.Chat.ViewModels; +using Softeq.XToolkit.Chat.Models; +using Softeq.XToolkit.Common.Models; namespace Softeq.XToolkit.Chat.Strategies.Search { public interface ISearchContactsStrategy { - Task> Search(string query, int pageNumber, int pageSize); + Task> Search(string query, int pageNumber, int pageSize); } } \ No newline at end of file diff --git a/Softeq.XToolkit.Chat/Strategies/Search/InviteSearchContactsStrategy.cs b/Softeq.XToolkit.Chat/Strategies/Search/InviteSearchContactsStrategy.cs index efa4acd..edb6e53 100644 --- a/Softeq.XToolkit.Chat/Strategies/Search/InviteSearchContactsStrategy.cs +++ b/Softeq.XToolkit.Chat/Strategies/Search/InviteSearchContactsStrategy.cs @@ -1,41 +1,29 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Softeq.XToolkit.Chat.Models; using Softeq.XToolkit.Chat.Models.Interfaces; -using Softeq.XToolkit.Chat.ViewModels; -using Softeq.XToolkit.WhiteLabel.Interfaces; +using Softeq.XToolkit.Common.Models; namespace Softeq.XToolkit.Chat.Strategies.Search { public class InviteSearchContactsStrategy : ISearchContactsStrategy { private readonly IChatService _chatService; - private readonly IViewModelFactoryService _viewModelFactoryService; private readonly string _chatId; public InviteSearchContactsStrategy( IChatService chatService, - IViewModelFactoryService viewModelFactoryService, string chatId) { _chatService = chatService; - _viewModelFactoryService = viewModelFactoryService; _chatId = chatId; } - public async Task> Search(string query, int pageNumber, int pageSize) + public Task> Search(string query, int pageNumber, int pageSize) { - var models = await _chatService.GetContactsForInviteAsync(_chatId, query, pageNumber, pageSize).ConfigureAwait(false); - - if (models == null) - { - return new List(); - } - return models.Data.Select(_viewModelFactoryService.ResolveViewModel).ToList(); + return _chatService.GetContactsForInviteAsync(_chatId, query, pageNumber, pageSize); } } } \ No newline at end of file diff --git a/Softeq.XToolkit.Chat/ViewModels/AddContactsViewModel.cs b/Softeq.XToolkit.Chat/ViewModels/AddContactsViewModel.cs index 3aa8210..61affd3 100644 --- a/Softeq.XToolkit.Chat/ViewModels/AddContactsViewModel.cs +++ b/Softeq.XToolkit.Chat/ViewModels/AddContactsViewModel.cs @@ -6,12 +6,14 @@ using System.Threading; using System.Threading.Tasks; using System.Windows.Input; +using Softeq.XToolkit.Chat.Models; using Softeq.XToolkit.Chat.Models.Enum; using Softeq.XToolkit.Chat.Models.Interfaces; using Softeq.XToolkit.Chat.Strategies.Search; using Softeq.XToolkit.Common.Collections; using Softeq.XToolkit.Common.Command; using Softeq.XToolkit.Common.Extensions; +using Softeq.XToolkit.Common.Models; using Softeq.XToolkit.WhiteLabel.Interfaces; using Softeq.XToolkit.WhiteLabel.Mvvm; @@ -27,9 +29,7 @@ public class AddContactParameters public class AddContactsViewModel : DialogViewModelBase, IViewModelParameter { private const int SearchDelayMs = 2000; - private const string InitialContactSearchQuery = ""; - private const int DefaultSearchResultsPageNumber = 1; - private const int DefaultSearchResultsPageSize = 20000; + private const int DefaultSearchResultsPageSize = 20; private readonly ICommand _contactSelectedCommand; @@ -38,10 +38,18 @@ public class AddContactsViewModel : DialogViewModelBase, IViewModelParameter _excludedContacts = new List(); - public AddContactsViewModel(IChatLocalizedStrings chatLocalizedStrings) + public AddContactsViewModel( + IChatLocalizedStrings chatLocalizedStrings, + IViewModelFactoryService viewModelFactoryService) { Resources = chatLocalizedStrings; + PaginationViewModel = new PaginationViewModel( + viewModelFactoryService, + SearchLoader, + SearchFilter, + DefaultSearchResultsPageSize); + _contactSelectedCommand = new RelayCommand(SwitchSelectedContact); SearchContactCommand = new RelayCommand(DoSearch); CancelCommand = new RelayCommand(() => DialogComponent.CloseCommand.Execute(false)); @@ -68,6 +76,8 @@ public AddContactParameters Parameter } } + public PaginationViewModel PaginationViewModel { get; } + public string Title { get; private set; } public string ContactNameSearchQuery @@ -75,25 +85,23 @@ public string ContactNameSearchQuery get => _contactNameSearchQuery; set { - Set(ref _contactNameSearchQuery, value); - - SearchContactCommand.Execute(value); + if (Set(ref _contactNameSearchQuery, value)) + { + SearchContactCommand.Execute(value); + } } } public ObservableRangeCollection SelectedContacts { get; } = new ObservableRangeCollection(); - public ObservableRangeCollection FoundContacts { get; } = - new ObservableRangeCollection(); - public bool HasSelectedContacts => SelectedContacts.Count > 0; public override async void OnAppearing() { base.OnAppearing(); - await LoadContactsAsync(InitialContactSearchQuery); + await PaginationViewModel.LoadFirstPageAsync(); } private async void DoSearch(string query) @@ -105,7 +113,7 @@ private async void DoSearch(string query) { await Task.Delay(SearchDelayMs, _lastSearchCancelSource.Token); - await LoadContactsAsync(query); + await PaginationViewModel.LoadFirstPageAsync(); } catch (TaskCanceledException) { @@ -113,28 +121,25 @@ private async void DoSearch(string query) } } - private async Task LoadContactsAsync(string query) + private Task> SearchLoader(int pageNumber, int pageSize) + { + return _searchContactsStrategy.Search(_contactNameSearchQuery, pageNumber, pageSize); + } + + private IReadOnlyList SearchFilter(IReadOnlyList contacts) { - FoundContacts.Clear(); + var filteredContacts = contacts.Where(x => !SelectedContacts + .Concat(_excludedContacts) + .Select(c => c.Id) + .Contains(x.Id) + ).ToList(); - var contacts = await _searchContactsStrategy.Search(query, - DefaultSearchResultsPageNumber, - DefaultSearchResultsPageSize).ConfigureAwait(false); + ApplySelectionCommand(filteredContacts, true); - if (contacts != null) - { - var filteredContacts = contacts.Where(x => !SelectedContacts - .Concat(_excludedContacts) - .Select(c => c.Id) - .Contains(x.Id) - ).ToList(); - - ApplySelectionCommand(filteredContacts); - FoundContacts.AddRange(filteredContacts); - } + return filteredContacts; } - private void ApplySelectionCommand(IEnumerable contacts, bool isSelectable = true) + private void ApplySelectionCommand(IEnumerable contacts, bool isSelectable) { contacts.Apply(x => { diff --git a/Softeq.XToolkit.Chat/ViewModels/ChatDetailsViewModel.cs b/Softeq.XToolkit.Chat/ViewModels/ChatDetailsViewModel.cs index a4f25fa..446ee6f 100644 --- a/Softeq.XToolkit.Chat/ViewModels/ChatDetailsViewModel.cs +++ b/Softeq.XToolkit.Chat/ViewModels/ChatDetailsViewModel.cs @@ -2,12 +2,11 @@ // http://www.softeq.com using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows.Input; -using Softeq.XToolkit.Chat.Manager; +using Softeq.XToolkit.Chat.Interfaces; using Softeq.XToolkit.Chat.Models; using Softeq.XToolkit.Chat.Models.Enum; using Softeq.XToolkit.Chat.Models.Interfaces; @@ -18,7 +17,6 @@ using Softeq.XToolkit.Chat.Strategies.Search; using Softeq.XToolkit.Common.Extensions; using Softeq.XToolkit.WhiteLabel; -using Softeq.XToolkit.WhiteLabel.Interfaces; using Softeq.XToolkit.WhiteLabel.Model; using Softeq.XToolkit.WhiteLabel.Threading; @@ -26,32 +24,29 @@ namespace Softeq.XToolkit.Chat.ViewModels { public class ChatDetailsViewModel : ViewModelBase { - private readonly ChatManager _chatManager; + private readonly IChatsListManager _chatsListManager; private readonly IPageNavigationService _pageNavigationService; private readonly IFormatService _formatService; private readonly IUploadImageService _uploadImageService; private readonly IDialogsService _dialogsService; private readonly IChatService _chatService; - private readonly IViewModelFactoryService _viewModelFactoryService; public ChatDetailsViewModel( - ChatManager chatManager, + IChatsListManager chatsListManager, IPageNavigationService pageNavigationService, IChatLocalizedStrings localizedStrings, IFormatService formatService, IUploadImageService uploadImageService, IDialogsService dialogsService, - IChatService chatService, - IViewModelFactoryService viewModelFactoryService) + IChatService chatService) { - _chatManager = chatManager; + _chatsListManager = chatsListManager; _pageNavigationService = pageNavigationService; LocalizedStrings = localizedStrings; _formatService = formatService; _uploadImageService = uploadImageService; _dialogsService = dialogsService; _chatService = chatService; - _viewModelFactoryService = viewModelFactoryService; AddMembersCommand = new RelayCommand(AddMembers); BackCommand = new RelayCommand(_pageNavigationService.GoBack, () => _pageNavigationService.CanGoBack); @@ -72,15 +67,13 @@ public ChatDetailsViewModel( public ICommand BackCommand { get; } public RelayCommand RemoveMemberAtCommand { get; } - public IList MenuActions { get; } = new List(); - public override async void OnAppearing() { base.OnAppearing(); Members.Clear(); - var members = await _chatManager.GetChatMembersAsync(Summary.Id); + var members = await _chatsListManager.GetChatMembersAsync(Summary.Id); Members.AddRange(members); RaisePropertyChanged(nameof(MembersCountText)); } @@ -130,7 +123,7 @@ public async Task SaveAsync(Func<(Task GetImageTask, string Extension)> Summary.AvatarUrl = imagePath; RaisePropertyChanged(nameof(Summary.AvatarUrl)); - await _chatManager.EditChatAsync(Summary).ConfigureAwait(false); + await _chatsListManager.EditChatAsync(Summary).ConfigureAwait(false); } Execute.BeginOnUIThread(() => @@ -146,8 +139,7 @@ private async void AddMembers() { SelectedContacts = Members, SelectionType = SelectedContactsAction.InviteToChat, - SearchStrategy = new InviteSearchContactsStrategy(_chatService, - _viewModelFactoryService, Summary.Id) + SearchStrategy = new InviteSearchContactsStrategy(_chatService, Summary.Id) }, new OpenDialogOptions { @@ -165,7 +157,7 @@ private async void AddMembers() try { - await _chatManager.InviteMembersAsync(Summary.Id, contactsForInvite); + await _chatsListManager.InviteMembersAsync(Summary.Id, contactsForInvite); } catch (Exception ex) { diff --git a/Softeq.XToolkit.Chat/ViewModels/ChatMessagesViewModel.cs b/Softeq.XToolkit.Chat/ViewModels/ChatMessagesViewModel.cs index 2f7d702..c841991 100644 --- a/Softeq.XToolkit.Chat/ViewModels/ChatMessagesViewModel.cs +++ b/Softeq.XToolkit.Chat/ViewModels/ChatMessagesViewModel.cs @@ -9,7 +9,6 @@ using System.Windows.Input; using Softeq.XToolkit.Chat.Manager; using Softeq.XToolkit.Chat.Models; -using Softeq.XToolkit.Chat.Models.Enum; using Softeq.XToolkit.Common.Collections; using Softeq.XToolkit.Common.Command; using Softeq.XToolkit.Common.EventArguments; @@ -19,6 +18,7 @@ using Softeq.XToolkit.WhiteLabel.Navigation; using Softeq.XToolkit.Chat.Models.Interfaces; using System.IO; +using Softeq.XToolkit.WhiteLabel.Threading; namespace Softeq.XToolkit.Chat.ViewModels { @@ -31,14 +31,15 @@ public class ChatMessagesViewModel : ViewModelBase, IViewModelParameter _subscriptions = new List(); - + private readonly IList _subscriptions = new List(); + private ChatSummaryViewModel _chatSummaryViewModel; - private ChatMessageViewModel _messageBeingEdited; - + private ChatMessageViewModel _messageBeingEdited; + private bool _areLatestMessagesLoaded; private string _messageToSendBody = string.Empty; - + private bool _isInEditMessageMode; + public ChatMessagesViewModel( IPageNavigationService pageNavigationService, IChatLocalizedStrings localizedStrings, @@ -67,12 +68,9 @@ public ChatSummaryViewModel Parameter { _chatSummaryViewModel = value; RaisePropertyChanged(nameof(ChatName)); - - // TODO: affected by different ways of register ViewModel on each platform - ClearMessages(); } - } - + } + public ObservableKeyGroupsCollection Messages { get; } = new ObservableKeyGroupsCollection(message => message.DateTime.Date, (x, y) => x.CompareTo(y), @@ -90,7 +88,11 @@ public string MessageToSendBody public string EditedMessageOriginalBody => _messageBeingEdited?.Body; - public bool IsInEditMessageMode { get; private set; } + public bool IsInEditMessageMode + { + get => _isInEditMessageMode; + private set => Set(ref _isInEditMessageMode, value); + } public ICommand BackCommand { get; } public RelayCommand, string)>>> SendCommand { get; } @@ -129,10 +131,10 @@ public override void OnAppearing() _subscriptions.Add(_chatManager.MessageDeleted.Subscribe(OnMessageDeleted)); _subscriptions.Add(_chatManager.MessagesBatchAdded.Subscribe(OnMessagesBatchReceived)); _subscriptions.Add(_chatManager.MessagesBatchUpdated.Subscribe(OnMessagesBatchUpdated)); - _subscriptions.Add(_chatManager.MessagesBatchDeleted.Subscribe(OnMessagesBatchDeleted)); - _subscriptions.Add(_chatManager.ConnectionStatusChanged.Subscribe(OnConnectionStatusChanged)); - OnConnectionStatusChanged(_chatManager.ConnectionStatus); - + _subscriptions.Add(_chatManager.MessagesBatchDeleted.Subscribe(OnMessagesBatchDeleted)); + + ConnectionStatusViewModel.Initialize(ChatName); + if (!_areLatestMessagesLoaded) { LoadInitialMessagesAsync().SafeTaskWrapper(); @@ -149,7 +151,9 @@ public override void OnDisappearing() base.OnDisappearing(); Messages.ItemsChanged -= OnMessagesAddedToCollection; - _subscriptions.Apply(x => x.Dispose()); + _subscriptions.Apply(x => x.Dispose()); + + ConnectionStatusViewModel.Dispose(); } public async Task LoadOlderMessagesAsync() @@ -255,12 +259,6 @@ private void OnMessagesBatchDeleted(IList deletedMessagesIds) DeleteAllMessages(x => deletedMessagesIds.Contains(x.Id)); } - private void OnConnectionStatusChanged(ConnectionStatus status) - { - ConnectionStatusViewModel.UpdateConnectionStatus(status, ChatName); - RaisePropertyChanged(nameof(ConnectionStatusViewModel)); - } - private void DeleteAllMessages(Func predicate) { var messagesToDelete = Messages @@ -273,23 +271,28 @@ private void DeleteAllMessages(Func predicate) private async void SendMessageAsync(GenericEventArgs, string)>> e) { + var photoSelector = e?.Value; var newMessageBody = MessageToSendBody?.Trim(); - if (string.IsNullOrEmpty(newMessageBody)) + + if (photoSelector == null && string.IsNullOrEmpty(newMessageBody)) { return; } MessageToSendBody = string.Empty; - if (IsInEditMessageMode && _messageBeingEdited != null) + if (IsInEditMessageMode) { - var editedMessageId = _messageBeingEdited.Id; + IsInEditMessageMode = false; + + await _chatManager.EditMessageAsync(_messageBeingEdited.Id, newMessageBody).ConfigureAwait(false); + CancelEditingMessageMode(); - await _chatManager.EditMessageAsync(editedMessageId, newMessageBody).ConfigureAwait(false); } else { - await _chatManager.SendMessageAsync(_chatSummaryViewModel.ChatId, newMessageBody, e?.Value).ConfigureAwait(false); + await _chatManager.SendMessageAsync(_chatSummaryViewModel.ChatId, newMessageBody, photoSelector) + .ConfigureAwait(false); } } @@ -317,24 +320,28 @@ private async void DeleteMessage(ChatMessageViewModel message) private void EditMessage(ChatMessageViewModel message) { - SetIsInEditMessageMode(true, message); + SetMessageEditMode(true, message); } private void CancelEditingMessageMode() { - SetIsInEditMessageMode(false); + SetMessageEditMode(false); } - private void SetIsInEditMessageMode(bool value, ChatMessageViewModel editedMessage = null) + private void SetMessageEditMode(bool value, ChatMessageViewModel editedMessage = null) { if (value && editedMessage == null) { return; } - IsInEditMessageMode = value; + _messageBeingEdited = value ? editedMessage : null; - MessageToSendBody = editedMessage?.Body; - RaisePropertyChanged(nameof(IsInEditMessageMode)); + + Execute.BeginOnUIThread(() => + { + MessageToSendBody = editedMessage?.Body; + IsInEditMessageMode = value; + }); } } } diff --git a/Softeq.XToolkit.Chat/ViewModels/ChatsListViewModel.cs b/Softeq.XToolkit.Chat/ViewModels/ChatsListViewModel.cs index e9e0b88..eb30914 100644 --- a/Softeq.XToolkit.Chat/ViewModels/ChatsListViewModel.cs +++ b/Softeq.XToolkit.Chat/ViewModels/ChatsListViewModel.cs @@ -1,12 +1,9 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using System; -using System.Collections.Generic; using System.Threading.Tasks; using System.Windows.Input; -using Softeq.XToolkit.Chat.Manager; -using Softeq.XToolkit.Chat.Models.Enum; +using Softeq.XToolkit.Chat.Interfaces; using Softeq.XToolkit.Chat.Models.Interfaces; using Softeq.XToolkit.Common.Collections; using Softeq.XToolkit.Common.Command; @@ -18,32 +15,26 @@ namespace Softeq.XToolkit.Chat.ViewModels { public class ChatsListViewModel : ViewModelBase { - private readonly IDialogsService _dialogsService; private readonly IPageNavigationService _pageNavigationService; - private readonly IChatLocalizedStrings _localizedStrings; - private readonly ChatManager _chatManager; - - private List _subscriptions = new List(); + private readonly IChatsListManager _chatsListManager; public ChatsListViewModel( - IDialogsService dialogsService, IPageNavigationService pageNavigationService, IChatLocalizedStrings localizedStrings, - ChatManager chatManager, + IChatsListManager chatsListManager, ConnectionStatusViewModel connectionStatusViewModel) { - _dialogsService = dialogsService; _pageNavigationService = pageNavigationService; - _localizedStrings = localizedStrings; - _chatManager = chatManager; - + _chatsListManager = chatsListManager; + + LocalizedStrings = localizedStrings; ConnectionStatusViewModel = connectionStatusViewModel; - Chats = _chatManager.ChatsCollection; + Chats = _chatsListManager.ChatsCollection; CreateChatCommand = new RelayCommand(CreateChat); - LeaveChatCommand = new RelayCommand((x) => LeaveChatAsync(x).SafeTaskWrapper()); - DeleteChatCommand = new RelayCommand((x) => DeleteChatAsync(x).SafeTaskWrapper()); + LeaveChatCommand = new RelayCommand(x => LeaveChatAsync(x).SafeTaskWrapper()); + DeleteChatCommand = new RelayCommand(x => DeleteChatAsync(x).SafeTaskWrapper()); } public ICommand CreateChatCommand { get; } @@ -52,38 +43,41 @@ public ChatsListViewModel( public ObservableRangeCollection Chats { get; } + public ConnectionStatusViewModel ConnectionStatusViewModel { get; } + + public IChatLocalizedStrings LocalizedStrings { get; } + public ChatSummaryViewModel SelectedChat { get => null; set { RaisePropertyChanged(); + if (value != null) { - _pageNavigationService.For().WithParam(x => x.Parameter, value).Navigate(); + _pageNavigationService + .For() + .WithParam(x => x.Parameter, value) + .Navigate(); } } } - public ConnectionStatusViewModel ConnectionStatusViewModel { get; } - - public string DeleteChatOptionText => _localizedStrings.Close; - public string LeaveChatOptionText => _localizedStrings.Leave; - public override void OnAppearing() { base.OnAppearing(); - _chatManager.ForceReconnect(); - - _subscriptions.Add(_chatManager.ConnectionStatusChanged.Subscribe(OnConnectionStatusChanged)); - OnConnectionStatusChanged(_chatManager.ConnectionStatus); + ConnectionStatusViewModel.Initialize(LocalizedStrings.ChatsTitle); + + _chatsListManager.RefreshChatsListOnBackgroundAsync(); } public override void OnDisappearing() { base.OnDisappearing(); - _subscriptions.Apply(x => x.Dispose()); + + ConnectionStatusViewModel.Dispose(); } private void CreateChat() @@ -93,18 +87,12 @@ private void CreateChat() private Task LeaveChatAsync(ChatSummaryViewModel chatViewModel) { - return _chatManager.LeaveChatAsync(chatViewModel.ChatId); + return _chatsListManager.LeaveChatAsync(chatViewModel.ChatId); } private Task DeleteChatAsync(ChatSummaryViewModel chatViewModel) { - return _chatManager.CloseChatAsync(chatViewModel.ChatId); - } - - private void OnConnectionStatusChanged(ConnectionStatus status) - { - ConnectionStatusViewModel.UpdateConnectionStatus(status, _localizedStrings.ChatsTitle); - RaisePropertyChanged(nameof(ConnectionStatusViewModel)); + return _chatsListManager.CloseChatAsync(chatViewModel.ChatId); } } } \ No newline at end of file diff --git a/Softeq.XToolkit.Chat/ViewModels/ConnectionStatusViewModel.cs b/Softeq.XToolkit.Chat/ViewModels/ConnectionStatusViewModel.cs index 535da24..316a626 100644 --- a/Softeq.XToolkit.Chat/ViewModels/ConnectionStatusViewModel.cs +++ b/Softeq.XToolkit.Chat/ViewModels/ConnectionStatusViewModel.cs @@ -1,32 +1,65 @@ // Developed by Softeq Development Corporation // http://www.softeq.com -using System.ComponentModel; +using System; +using System.ComponentModel; +using Softeq.XToolkit.Chat.Exceptions; +using Softeq.XToolkit.Chat.Interfaces; using Softeq.XToolkit.Chat.Models.Enum; using Softeq.XToolkit.Chat.Models.Interfaces; using Softeq.XToolkit.WhiteLabel.Mvvm; namespace Softeq.XToolkit.Chat.ViewModels { - public class ConnectionStatusViewModel : ObservableObject + public class ConnectionStatusViewModel : ObservableObject, IDisposable { private readonly IChatLocalizedStrings _localizedStrings; + private readonly IChatConnectionManager _chatConnectionManager; + + private IDisposable _connectionStatusChangedSubscription; + private string _onlineTextStatus; + private string _connectionStatusText; + private bool _isOnline; - public ConnectionStatusViewModel(IChatLocalizedStrings localizedStrings) + public ConnectionStatusViewModel( + IChatLocalizedStrings localizedStrings, + IChatConnectionManager chatConnectionManager) { _localizedStrings = localizedStrings; + _chatConnectionManager = chatConnectionManager; } - public string ConnectionStatusText { get; private set; } + public string ConnectionStatusText + { + get => _connectionStatusText; + private set => Set(ref _connectionStatusText, value); + } + + public bool IsOnline + { + get => _isOnline; + private set => Set(ref _isOnline, value); + } - public bool IsOnline { get; private set; } + public void Initialize(string onlineTextStatus) + { + _onlineTextStatus = onlineTextStatus ?? throw new ArgumentNullException(nameof(onlineTextStatus)); + _connectionStatusChangedSubscription = _chatConnectionManager.ConnectionStatusChanged.Subscribe(UpdateConnectionStatus); - public void UpdateConnectionStatus(ConnectionStatus status, string onlineStatusText) + UpdateConnectionStatus(_chatConnectionManager.ConnectionStatus); + } + + private void UpdateConnectionStatus(ConnectionStatus status) { + if (_onlineTextStatus == null) + { + throw new ChatException("Need to call Initialize() method before."); + } + switch (status) { case ConnectionStatus.Online: - ConnectionStatusText = onlineStatusText; + ConnectionStatusText = _onlineTextStatus; break; case ConnectionStatus.WaitingForNetwork: ConnectionStatusText = _localizedStrings.WaitingForNetwork; @@ -42,9 +75,20 @@ public void UpdateConnectionStatus(ConnectionStatus status, string onlineStatusT } IsOnline = status == ConnectionStatus.Online; + } + + private void Dispose(bool disposing) + { + if (disposing) + { + _connectionStatusChangedSubscription?.Dispose(); + } + } - RaisePropertyChanged(nameof(ConnectionStatusText)); - RaisePropertyChanged(nameof(IsOnline)); + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); } } } diff --git a/Softeq.XToolkit.Chat/ViewModels/PaginationViewModel.cs b/Softeq.XToolkit.Chat/ViewModels/PaginationViewModel.cs new file mode 100644 index 0000000..869817c --- /dev/null +++ b/Softeq.XToolkit.Chat/ViewModels/PaginationViewModel.cs @@ -0,0 +1,91 @@ +// Developed by Softeq Development Corporation +// http://www.softeq.com + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Softeq.XToolkit.Common.Collections; +using Softeq.XToolkit.Common.Models; +using Softeq.XToolkit.WhiteLabel.Interfaces; +using Softeq.XToolkit.WhiteLabel.Mvvm; + +namespace Softeq.XToolkit.Chat.ViewModels +{ + public class PaginationViewModel + where TViewModel : ObservableObject, IViewModelParameter + { + private readonly IViewModelFactoryService _viewModelFactoryService; + private readonly Func>> _loaderAction; + private readonly Func, IReadOnlyList> _filterAction; + private readonly int _pageSize; + + private int _pageNumber = 1; + + public PaginationViewModel( + IViewModelFactoryService viewModelFactory, + Func>> loaderAction, + Func, IReadOnlyList> filterAction, + int pageSize) + { + _viewModelFactoryService = viewModelFactory ?? throw new ArgumentNullException(nameof(viewModelFactory)); + _loaderAction = loaderAction ?? throw new ArgumentNullException(nameof(loaderAction)); + _filterAction = filterAction; + _pageSize = pageSize; + } + + public ObservableRangeCollection Items { get; } = new ObservableRangeCollection(); + + public async Task LoadFirstPageAsync() + { + _pageNumber = 1; + + var viewModels = await LoadPageAsync(_pageNumber).ConfigureAwait(false); + + Items.ReplaceRange(viewModels); + } + + public async Task LoadNextPageAsync() + { + Interlocked.Increment(ref _pageNumber); + + var viewModels = await LoadPageAsync(_pageNumber).ConfigureAwait(false); + + if (viewModels.Count > 0) + { + Items.AddRange(viewModels); + } + else + { + Interlocked.Decrement(ref _pageNumber); + } + } + + private async Task> LoadPageAsync(int pageNumber) + { + var pagingModel = await _loaderAction(pageNumber, _pageSize).ConfigureAwait(false); + if (pagingModel == null) + { + return new List(); + } + + if (pagingModel.Page == pagingModel.TotalNumberOfPages && + pagingModel.Data.Count == 0) + { + return new List(); + } + + var viewModels = pagingModel.Data + .Select(_viewModelFactoryService.ResolveViewModel) + .ToList(); + + if (_filterAction != null) + { + return _filterAction(viewModels); + } + + return viewModels; + } + } +} \ No newline at end of file diff --git a/Softeq.XToolkit.Chat/ViewModels/SelectContactsViewModel.cs b/Softeq.XToolkit.Chat/ViewModels/SelectContactsViewModel.cs index 5b0d572..e229de3 100644 --- a/Softeq.XToolkit.Chat/ViewModels/SelectContactsViewModel.cs +++ b/Softeq.XToolkit.Chat/ViewModels/SelectContactsViewModel.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Threading.Tasks; using System.Windows.Input; -using Softeq.XToolkit.Chat.Manager; +using Softeq.XToolkit.Chat.Interfaces; using Softeq.XToolkit.Chat.Models.Enum; using Softeq.XToolkit.Chat.Models.Interfaces; using Softeq.XToolkit.Chat.Strategies.Search; @@ -21,27 +21,31 @@ namespace Softeq.XToolkit.Chat.ViewModels { + // TODO YP: rename to CreateChatViewModel or merge with ChatDetailsViewModel public class SelectContactsViewModel : ViewModelBase { - private readonly ChatManager _chatManager; + private readonly IChatsListManager _chatsListManager; + private readonly IChatService _chatService; private readonly IFormatService _formatService; private readonly IChatLocalizedStrings _localizedStrings; private readonly ICommand _memberSelectedCommand; private readonly IPageNavigationService _pageNavigationService; private readonly IUploadImageService _uploadImageService; private readonly IDialogsService _dialogsService; - + private string _chatName; public SelectContactsViewModel( - ChatManager chatManager, + IChatsListManager chatsListManager, + IChatService chatService, IPageNavigationService pageNavigationService, IChatLocalizedStrings localizedStrings, IFormatService formatService, IUploadImageService uploadImageService, IDialogsService dialogsService) { - _chatManager = chatManager; + _chatsListManager = chatsListManager; + _chatService = chatService; _pageNavigationService = pageNavigationService; _localizedStrings = localizedStrings; _formatService = formatService; @@ -94,7 +98,7 @@ private async void OpenDialogForAddMembers() { SelectedContacts = Contacts, SelectionType = SelectedContactsAction.CreateChat, - SearchStrategy = new CreateChatSearchContactsStrategy(_chatManager) + SearchStrategy = new CreateChatSearchContactsStrategy(_chatService) }, new OpenDialogOptions { @@ -113,12 +117,7 @@ private async void OpenDialogForAddMembers() private async void SaveAsync(Func<(Task GetImageTask, string Extension)> getImageFunc) { - if (string.IsNullOrEmpty(ChatName)) - { - return; - } - - if (IsBusy) + if (IsBusy || string.IsNullOrEmpty(ChatName)) { return; } @@ -140,12 +139,12 @@ private async void SaveAsync(Func<(Task GetImageTask, string Extension)> try { var selectedContactsIds = Contacts.Where(x => x.IsSelected).Select(x => x.Id).ToList(); - await _chatManager.CreateChatAsync(ChatName, selectedContactsIds, imagePath).ConfigureAwait(false); - + await _chatsListManager.CreateChatAsync(ChatName, selectedContactsIds, imagePath).ConfigureAwait(false); + Execute.BeginOnUIThread(() => { ChatName = string.Empty; - + _pageNavigationService.GoBack(); }); }