Serious fixes to loading mechanism

This commit is contained in:
Mario Danic 2019-12-30 02:05:45 +01:00 committed by Mario Đanić
parent f311aa1d16
commit 3767b7fd61
34 changed files with 319 additions and 299 deletions

View File

@ -48,7 +48,7 @@ import java.util.regex.Pattern
class ConversationItem(
val model: Conversation,
private val user: UserNgEntity,
val user: UserNgEntity,
private val context: Context
) : AbstractFlexibleItem<ConversationItem.ConversationItemViewHolder>(), IFilterable<String> {
@ -56,6 +56,7 @@ class ConversationItem(
if (other is ConversationItem) {
val inItem = other as ConversationItem?
val comparedConversation = inItem!!.model
return (model.conversationId == comparedConversation.conversationId
&& model.token == comparedConversation.token
&& model.name == comparedConversation.name
@ -68,7 +69,7 @@ class ConversationItem(
&& model.unreadMention == comparedConversation.unreadMention
&& model.objectType == comparedConversation.objectType
&& model.changing == comparedConversation.changing
&& inItem.user.id == other.user.id)
&& inItem.user.id == user.id)
}
return false
}
@ -199,7 +200,6 @@ class ConversationItem(
holder.itemView.dialogAvatar.visibility = View.VISIBLE
val conversationDrawable: Drawable? = Images().getImageForConversation(context, model)
if (conversationDrawable != null) {
holder.itemView.dialogAvatar.load(conversationDrawable)

View File

@ -24,7 +24,6 @@ import android.content.Intent
import android.net.Uri
import android.text.Spannable
import android.text.SpannableString
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.widget.ImageView

View File

@ -1412,7 +1412,8 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
messageInputView?.findViewById<EmojiTextView>(R.id.quotedMessage)?.ellipsize = TextUtils.TruncateAt.END
messageInputView?.findViewById<EmojiTextView>(R.id.quotedMessage)?.text = it.text
messageInputView?.findViewById<TextView>(R.id.quotedMessageTime)?.text = DateFormatter.format(it.createdAt, DateFormatter.Template.TIME)
messageInputView?.findViewById<EmojiTextView>(R.id.quotedMessageAuthor)?.text = it.actorDisplayName ?: context.getText(R.string.nc_nick_guest)
messageInputView?.findViewById<EmojiTextView>(R.id.quotedMessageAuthor)?.text = it.actorDisplayName
?: context.getText(R.string.nc_nick_guest)
conversationUser?.let { currentUser ->
messageInputView?.findViewById<ImageView>(R.id.quotedUserAvatar)?.load(it.user.avatar) {

View File

@ -21,8 +21,6 @@
package com.nextcloud.talk.controllers
import android.content.Intent
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
@ -61,7 +59,8 @@ import com.nextcloud.talk.jobs.DeleteConversationWorker
import com.nextcloud.talk.jobs.LeaveConversationWorker
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType
import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType.*
import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType.PUBLIC_CONVERSATION
import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType.SYSTEM_CONVERSATION
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.converters.EnumNotificationLevelConverter
import com.nextcloud.talk.models.json.generic.GenericOverall

View File

@ -297,7 +297,7 @@ class SettingsController : BaseController() {
var hasMultipleUsers = false
val job = async {
currentUser = usersRepository.getActiveUser()
hasMultipleUsers = usersRepository.getUsers().size > 0
hasMultipleUsers = usersRepository.getUsers().isNotEmpty()
credentials = currentUser!!.getCredentials()
}

View File

@ -26,6 +26,7 @@ import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.android.parcel.Parcelize
import lombok.Data
import org.parceler.Parcel
import java.util.*
@Data
@Parcel
@ -44,14 +45,15 @@ data class ExternalSignalingServer(
other as ExternalSignalingServer
if (externalSignalingServer != other.externalSignalingServer) return false
if (externalSignalingTicket != other.externalSignalingTicket) return false
//if (externalSignalingTicket != other.externalSignalingTicket) return false
return true
}
override fun hashCode(): Int {
var result = externalSignalingServer?.hashCode() ?: 0
return Objects.hash(externalSignalingServer)
/*var result = externalSignalingServer?.hashCode() ?: 0
result = 31 * result + (externalSignalingTicket?.hashCode() ?: 0)
return result
return result*/
}
}

View File

@ -42,6 +42,7 @@ data class Capabilities(
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is Capabilities) return false
return (spreedCapability == o.spreedCapability &&
notificationsCapability == o.notificationsCapability &&
themingCapability == o.themingCapability)

View File

@ -41,9 +41,8 @@ data class SpreedCapability(
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is SpreedCapability) return false
val that = o
return features == that.features &&
config == that.config
return features == o.features &&
config == o.config
}
override fun hashCode(): Int {

View File

@ -44,6 +44,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import lombok.Data;
@ -97,13 +98,35 @@ public class ChatMessage implements IMessage, MessageContentType, MessageContent
public boolean replyable;
@JsonField(name = "parent")
public ChatMessage parentMessage;
@JsonIgnore
@Ignore
List<MessageType> messageTypesToIgnore = Arrays.asList(MessageType.REGULAR_TEXT_MESSAGE,
MessageType.SYSTEM_MESSAGE, MessageType.SINGLE_LINK_VIDEO_MESSAGE,
MessageType.SINGLE_LINK_AUDIO_MESSAGE, MessageType.SINGLE_LINK_MESSAGE);
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ChatMessage)) return false;
ChatMessage that = (ChatMessage) o;
return timestamp == that.timestamp &&
replyable == that.replyable &&
Objects.equals(jsonMessageId, that.jsonMessageId) &&
Objects.equals(actorType, that.actorType) &&
Objects.equals(actorId, that.actorId) &&
Objects.equals(actorDisplayName, that.actorDisplayName) &&
Objects.equals(token, that.token) &&
Objects.equals(message, that.message) &&
Objects.equals(messageParameters, that.messageParameters) &&
systemMessageType == that.systemMessageType &&
Objects.equals(parentMessage, that.parentMessage);
}
@Override
public int hashCode() {
return Objects.hash(jsonMessageId, token, actorType, actorId, actorDisplayName, timestamp, message, messageParameters, systemMessageType, replyable, parentMessage);
}
private boolean hasFileAttachment() {
if (messageParameters != null && messageParameters.size() > 0) {
for (String key : messageParameters.keySet()) {

View File

@ -22,15 +22,15 @@
package com.nextcloud.talk.models.json.converters
import com.bluelinelabs.logansquare.typeconverters.LongBasedTypeConverter
import com.bluelinelabs.logansquare.typeconverters.IntBasedTypeConverter
import com.nextcloud.talk.models.json.participants.Participant
class EnumParticipantFlagsConverter : LongBasedTypeConverter<Participant.ParticipantFlags>() {
override fun getFromLong(l: Long): Participant.ParticipantFlags {
class EnumParticipantFlagsConverter : IntBasedTypeConverter<Participant.ParticipantFlags>() {
override fun getFromInt(l: Int): Participant.ParticipantFlags {
return Participant.ParticipantFlags.fromValue(l)
}
override fun convertToLong(`object`: Participant.ParticipantFlags?): Long {
override fun convertToInt(`object`: Participant.ParticipantFlags?): Int {
return `object`?.value ?: 0
}
}

View File

@ -27,6 +27,8 @@ import com.nextcloud.talk.models.json.converters.EnumParticipantTypeConverter;
import org.parceler.Parcel;
import java.util.Objects;
import lombok.Data;
@Parcel
@ -47,8 +49,8 @@ public class Participant {
@JsonField(name = "displayName")
public String displayName;
@JsonField(name = "lastPing")
public long lastPing;
/*@JsonField(name = "lastPing")
public long lastPing;*/
@JsonField(name = "sessionId")
public String sessionId;
@ -64,6 +66,27 @@ public class Participant {
public boolean selected;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Participant)) return false;
Participant that = (Participant) o;
return conversationId == that.conversationId &&
selected == that.selected &&
Objects.equals(userId, that.userId) &&
type == that.type &&
Objects.equals(name, that.name) &&
Objects.equals(displayName, that.displayName) &&
Objects.equals(sessionId, that.sessionId) &&
participantFlags == that.participantFlags &&
Objects.equals(source, that.source);
}
@Override
public int hashCode() {
return Objects.hash(userId, type, name, displayName, sessionId, conversationId, participantFlags, source, selected);
}
public enum ParticipantType {
OWNER(1),
MODERATOR(2),
@ -72,13 +95,13 @@ public class Participant {
USER_FOLLOWING_LINK(5),
GUEST_AS_MODERATOR(6);
private long value;
private Integer value;
ParticipantType(long value) {
ParticipantType(Integer value) {
this.value = value;
}
public static ParticipantType fromValue(long value) {
public static ParticipantType fromValue(Integer value) {
if (value == 1) {
return OWNER;
} else if (value == 2) {
@ -96,7 +119,7 @@ public class Participant {
}
}
public long getValue() {
public Integer getValue() {
return value;
}
@ -109,13 +132,13 @@ public class Participant {
IN_CALL_WITH_VIDEO(5),
IN_CALL_WITH_AUDIO_AND_VIDEO(7);
private long value;
private Integer value;
ParticipantFlags(long value) {
ParticipantFlags(Integer value) {
this.value = value;
}
public static ParticipantFlags fromValue(long value) {
public static ParticipantFlags fromValue(Integer value) {
if (value == 0) {
return NOT_IN_CALL;
} else if (value == 1) {
@ -131,7 +154,7 @@ public class Participant {
}
}
public long getValue() {
public Integer getValue() {
return value;
}
}

View File

@ -7,7 +7,7 @@ import com.nextcloud.talk.newarch.domain.repository.offline.MessagesRepository
import com.nextcloud.talk.newarch.domain.repository.online.NextcloudTalkRepository
import com.nextcloud.talk.newarch.domain.usecases.*
import com.nextcloud.talk.newarch.features.chat.ChatViewModelFactory
import com.nextcloud.talk.newarch.utils.GlobalService
import com.nextcloud.talk.newarch.services.GlobalService
import org.koin.dsl.module
val ConversationsModule = module {

View File

@ -5,7 +5,7 @@ import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsReposit
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.domain.usecases.GetConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.JoinConversationUseCase
import com.nextcloud.talk.newarch.utils.GlobalService
import com.nextcloud.talk.newarch.services.GlobalService
import com.nextcloud.talk.newarch.utils.ShortcutService
import okhttp3.OkHttpClient
import org.koin.dsl.module

View File

@ -11,8 +11,8 @@ import com.nextcloud.talk.newarch.domain.repository.offline.MessagesRepository
import com.nextcloud.talk.newarch.domain.usecases.ExitConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.JoinConversationUseCase
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.utils.GlobalService
import com.nextcloud.talk.newarch.utils.GlobalServiceInterface
import com.nextcloud.talk.newarch.services.GlobalService
import com.nextcloud.talk.newarch.services.GlobalServiceInterface
import kotlinx.coroutines.launch
class ChatViewModel constructor(application: Application,

View File

@ -7,7 +7,7 @@ import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsReposit
import com.nextcloud.talk.newarch.domain.repository.offline.MessagesRepository
import com.nextcloud.talk.newarch.domain.usecases.ExitConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.JoinConversationUseCase
import com.nextcloud.talk.newarch.utils.GlobalService
import com.nextcloud.talk.newarch.services.GlobalService
class ChatViewModelFactory constructor(
private val application: Application,

View File

@ -24,11 +24,11 @@ import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsRepository
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.domain.usecases.DeleteConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.SetConversationFavoriteValueUseCase
import com.nextcloud.talk.newarch.services.GlobalService
class ConversationListViewModelFactory constructor(
private val application: Application,
@ -37,14 +37,14 @@ class ConversationListViewModelFactory constructor(
private val leaveConversationUseCase: LeaveConversationUseCase,
private val deleteConversationUseCase: DeleteConversationUseCase,
private val conversationsRepository: ConversationsRepository,
private val usersRepository: UsersRepository
private val globalService: GlobalService
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return ConversationsListViewModel(
application, conversationsUseCase,
setConversationFavoriteValueUseCase, leaveConversationUseCase, deleteConversationUseCase,
conversationsRepository, usersRepository
conversationsRepository, globalService
) as T
}
}

View File

@ -23,7 +23,6 @@ package com.nextcloud.talk.newarch.features.conversationsList
import android.app.SearchManager
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.text.InputType
@ -32,13 +31,10 @@ import android.view.inputmethod.EditorInfo
import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.SearchView.OnQueryTextListener
import androidx.core.view.MenuItemCompat
import androidx.lifecycle.MutableLiveData
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import butterknife.OnClick
import coil.ImageLoader
import coil.target.Target
import coil.transform.CircleCropTransformation
import com.afollestad.materialdialogs.LayoutMode.WRAP_CONTENT
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
@ -55,10 +51,8 @@ import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage
import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseView
import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListViewState.*
import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListViewNetworkState.*
import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView
import com.nextcloud.talk.newarch.utils.Images
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.ConductorRemapping
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.ShareUtils
@ -81,8 +75,6 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
private lateinit var viewModel: ConversationsListViewModel
val factory: ConversationListViewModelFactory by inject()
private val imageLoader: ImageLoader by inject()
private val viewState: MutableLiveData<ConversationsListViewState> = MutableLiveData(LOADING)
private val recyclerViewAdapter = FlexibleAdapter(mutableListOf(), null, false)
@ -109,42 +101,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
}
settingsItem = menu.findItem(R.id.action_settings)
}
private fun loadAvatar() {
val iconSize = settingsItem?.icon?.intrinsicHeight?.toFloat()
?.let {
DisplayUtils.convertDpToPixel(
it,
activity!!
)
.toInt()
}
iconSize?.let {
val target = object : Target {
override fun onSuccess(result: Drawable) {
super.onSuccess(result)
settingsItem?.icon = result
}
override fun onError(error: Drawable?) {
super.onError(error)
settingsItem?.icon = context.getDrawable(R.drawable.ic_settings_white_24dp)
}
}
viewModel.currentUserLiveData.value?.let {
val avatarRequest = Images().getRequestForUrl(
imageLoader, context, ApiUtils.getUrlForAvatarWithNameAndPixels(
it.baseUrl,
it.userId, iconSize
), it, target, this, CircleCropTransformation()
)
imageLoader.load(avatarRequest)
}
}
viewModel.loadAvatar()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -186,10 +143,6 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
searchView!!.setOnQueryTextListener(this)
}
override fun onRestoreViewState(view: View, savedViewState: Bundle) {
super.onRestoreViewState(view, savedViewState)
viewModel.loadConversations()
}
override fun onQueryTextSubmit(query: String?): Boolean {
if (!viewModel.searchQuery.value.equals(query)) {
@ -212,26 +165,32 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
viewModel = viewModelProvider(factory).get(ConversationsListViewModel::class.java)
viewModel.apply {
currentUserLiveData.observe(this@ConversationsListView, Observer { value ->
loadAvatar()
currentUserAvatar.observe(this@ConversationsListView, Observer { value ->
settingsItem?.icon = value
})
conversationsLiveData.observe(this@ConversationsListView, Observer {
if (it.isEmpty()) {
if (viewState.value != LOADED_EMPTY) {
viewState.value = LOADED_EMPTY
}
} else {
if (viewState.value != LOADED) {
viewState.value = LOADED
val isListEmpty = it.isNullOrEmpty()
if (isListEmpty) {
view?.stateWithMessageView?.errorStateTextView?.text =
resources?.getText(R.string.nc_conversations_empty)
view?.stateWithMessageView?.errorStateImageView?.setImageResource(drawable.ic_logo)
}
view?.stateWithMessageView?.visibility = if (isListEmpty && networkStateLiveData.value != LOADING) View.VISIBLE else View.GONE
if (view?.floatingActionButton?.isShown == false) {
view?.floatingActionButton?.show()
}
searchItem?.isVisible = !isListEmpty
val newConversations = mutableListOf<ConversationItem>()
for (conversation in it) {
newConversations.add(
ConversationItem(
conversation, viewModel.currentUserLiveData.value!!,
conversation, globalService.currentUserLiveData.value!!,
activity!!
)
)
@ -241,6 +200,46 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
newConversations as
List<IFlexible<ViewHolder>>?, false
)
})
networkStateLiveData.observe(this@ConversationsListView, Observer { value ->
when (value) {
LOADING -> {
view?.post {
view?.loadingStateView?.visibility = View.VISIBLE
view?.dataStateView?.visibility = View.GONE
view?.stateWithMessageView?.visibility = View.GONE
view?.floatingActionButton?.visibility = View.GONE
}
searchItem?.isVisible = false
}
LOADED -> {
// awesome, but we delegate the magic stuff to the data handler
view?.post {
view?.loadingStateView?.visibility = View.GONE
view?.dataStateView?.visibility = View.VISIBLE
view?.floatingActionButton?.visibility = View.VISIBLE
if (view?.floatingActionButton?.isShown == false) {
view?.floatingActionButton?.show()
}
}
searchItem?.isVisible = !recyclerViewAdapter.isEmpty
}
FAILED -> {
// probably offline, so what? :)
view?.post {
view?.loadingStateView?.visibility = View.GONE
view?.dataStateView?.visibility = View.VISIBLE
view?.floatingActionButton?.visibility = View.GONE
view?.stateWithMessageView?.visibility = if (recyclerViewAdapter.isEmpty) View.VISIBLE else View.GONE
}
searchItem?.isVisible = !recyclerViewAdapter.isEmpty
}
else -> {
// We should not be here
}
}
})
searchQuery.observe(this@ConversationsListView, Observer {
@ -249,65 +248,6 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
})
}
viewState.observe(this@ConversationsListView, Observer { value ->
when (value) {
LOADING -> {
view?.swipeRefreshLayoutView?.isEnabled = false
view?.loadingStateView?.visibility = View.VISIBLE
view?.stateWithMessageView?.visibility = View.GONE
view?.dataStateView?.visibility = View.GONE
view?.floatingActionButton?.visibility = View.GONE
searchItem?.isVisible = false
}
LOADED -> {
view?.swipeRefreshLayoutView?.isEnabled = true
view?.swipeRefreshLayoutView?.post {
view?.swipeRefreshLayoutView?.isRefreshing = false
}
view?.loadingStateView?.visibility = View.GONE
view?.stateWithMessageView?.visibility = View.GONE
view?.dataStateView?.visibility = View.VISIBLE
view?.floatingActionButton?.visibility = View.VISIBLE
searchItem?.isVisible = true
}
LOADED_EMPTY, FAILED -> {
view?.swipeRefreshLayoutView?.post {
view?.swipeRefreshLayoutView?.isRefreshing = false
}
view?.swipeRefreshLayoutView?.isEnabled = true
view?.loadingStateView?.visibility = View.GONE
view?.dataStateView?.visibility = View.GONE
searchItem?.isVisible = false
if (value.equals(FAILED)) {
view?.stateWithMessageView?.errorStateTextView?.text = viewModel.messageData
if (viewModel.messageData.equals(
context.resources.getString(R.string.nc_no_connection_error)
)
) {
view?.stateWithMessageView?.errorStateImageView?.setImageResource(
drawable.ic_signal_wifi_off_white_24dp
)
} else {
view?.stateWithMessageView?.errorStateImageView?.setImageResource(
drawable.ic_announcement_white_24dp
)
}
view?.floatingActionButton?.visibility = View.GONE
} else {
view?.floatingActionButton?.visibility = View.VISIBLE
view?.stateWithMessageView?.errorStateTextView?.text =
resources?.getText(R.string.nc_conversations_empty)
view?.stateWithMessageView?.errorStateImageView?.setImageResource(drawable.ic_logo)
}
view?.stateWithMessageView?.visibility = View.VISIBLE
}
else -> {
// We should not be here
}
}
})
return super.onCreateView(inflater, container)
@ -324,7 +264,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
@OnClick(R.id.stateWithMessageView)
fun onStateWithMessageViewClick() {
if (viewState.value!! == LOADED_EMPTY) {
if (view?.floatingActionButton?.isVisible == true) {
openNewConversationScreen()
}
}
@ -392,7 +332,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
)
}
if (conversation.canLeave(viewModel.currentUserLiveData.value!!)) {
if (conversation.canLeave(viewModel.globalService.currentUserLiveData.value!!)) {
items.add(
BasicListItemWithImage(
drawable.ic_exit_to_app_black_24dp, context.getString
@ -401,7 +341,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
)
}
if (conversation.canModerate(viewModel.currentUserLiveData.value!!)) {
if (conversation.canModerate(viewModel.globalService.currentUserLiveData.value!!)) {
items.add(
BasicListItemWithImage(
drawable.ic_delete_grey600_24dp, context.getString(
@ -418,6 +358,11 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
return resources?.getString(R.string.nc_app_name)
}
override fun onRestoreViewState(view: View, savedViewState: Bundle) {
super.onRestoreViewState(view, savedViewState)
viewModel.loadConversations()
}
override fun onAttach(view: View) {
super.onAttach(view)
view.recyclerView.initRecyclerView(
@ -432,7 +377,6 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
view.swipeRefreshLayoutView.isRefreshing = false
viewModel.loadConversations()
}
view.swipeRefreshLayoutView.setColorSchemeResources(R.color.colorPrimary)
view.fast_scroller.setBubbleTextCreator { position ->
@ -514,15 +458,16 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
): Boolean {
val clickedItem = recyclerViewAdapter.getItem(position)
if (clickedItem != null) {
val conversation = (clickedItem as ConversationItem).model
val conversationItem = clickedItem as ConversationItem
val conversation = conversationItem.model
val bundle = Bundle()
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, viewModel.currentUserLiveData.value)
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, conversationItem.user)
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation.token)
bundle.putString(BundleKeys.KEY_ROOM_ID, conversation.conversationId)
bundle.putParcelable(BundleKeys.KEY_ACTIVE_CONVERSATION, Parcels.wrap(conversation))
ConductorRemapping.remapChatController(
router, viewModel.currentUserLiveData.value!!.id!!, conversation.token!!,
router, conversationItem.user.id!!, conversation.token!!,
bundle, false
)
}

View File

@ -21,30 +21,31 @@
package com.nextcloud.talk.newarch.features.conversationsList
import android.app.Application
import android.content.Intent
import androidx.lifecycle.LiveData
import android.graphics.drawable.Drawable
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.viewModelScope
import coil.Coil
import coil.api.get
import coil.transform.CircleCropTransformation
import com.nextcloud.talk.R
import com.nextcloud.talk.R.drawable
import com.nextcloud.talk.R.string
import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel
import com.nextcloud.talk.newarch.data.model.ErrorModel
import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsRepository
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.domain.usecases.DeleteConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.SetConversationFavoriteValueUseCase
import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.utils.ShareUtils
import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.newarch.services.GlobalService
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import kotlinx.coroutines.launch
import org.koin.core.parameter.parametersOf
import java.util.concurrent.locks.ReentrantLock
class ConversationsListViewModel constructor(
application: Application,
@ -53,14 +54,21 @@ class ConversationsListViewModel constructor(
private val leaveConversationUseCase: LeaveConversationUseCase,
private val deleteConversationUseCase: DeleteConversationUseCase,
private val conversationsRepository: ConversationsRepository,
usersRepository: UsersRepository
val globalService: GlobalService
) : BaseViewModel<ConversationsListView>(application) {
private val conversationsLoadingLock = ReentrantLock()
var messageData: String? = null
val searchQuery = MutableLiveData<String>()
val currentUserLiveData: LiveData<UserNgEntity> = usersRepository.getActiveUserLiveData()
val conversationsLiveData = Transformations.switchMap(currentUserLiveData) {
val networkStateLiveData: MutableLiveData<ConversationsListViewNetworkState> = MutableLiveData(ConversationsListViewNetworkState.LOADING)
val currentUserAvatar: MutableLiveData<Drawable> = MutableLiveData(DisplayUtils.getRoundedDrawable(context.getDrawable(R.drawable.ic_settings_white_24dp)))
val conversationsLiveData = Transformations.switchMap(globalService.currentUserLiveData) {
if (networkStateLiveData.value != ConversationsListViewNetworkState.LOADING) {
networkStateLiveData.postValue(ConversationsListViewNetworkState.LOADING)
}
loadConversations()
loadAvatar()
conversationsRepository.getConversationsForUser(it.id!!)
}
@ -70,13 +78,13 @@ class ConversationsListViewModel constructor(
}
leaveConversationUseCase.invoke(viewModelScope, parametersOf(
currentUserLiveData.value,
globalService.currentUserLiveData.value,
conversation
),
object : UseCaseResponse<GenericOverall> {
override suspend fun onSuccess(result: GenericOverall) {
conversationsRepository.deleteConversation(
currentUserLiveData.value!!.id!!, conversation
globalService.currentUserLiveData.value!!.id!!, conversation
.conversationId!!
)
}
@ -99,13 +107,13 @@ class ConversationsListViewModel constructor(
}
deleteConversationUseCase.invoke(viewModelScope, parametersOf(
currentUserLiveData.value,
globalService.currentUserLiveData.value,
conversation
),
object : UseCaseResponse<GenericOverall> {
override suspend fun onSuccess(result: GenericOverall) {
conversationsRepository.deleteConversation(
currentUserLiveData.value!!.id!!, conversation
globalService.currentUserLiveData.value!!.id!!, conversation
.conversationId!!
)
}
@ -129,14 +137,14 @@ class ConversationsListViewModel constructor(
}
setConversationFavoriteValueUseCase.invoke(viewModelScope, parametersOf(
currentUserLiveData.value,
globalService.currentUserLiveData.value,
conversation,
favorite
),
object : UseCaseResponse<GenericOverall> {
override suspend fun onSuccess(result: GenericOverall) {
conversationsRepository.setFavoriteValueForConversation(
currentUserLiveData.value!!.id!!,
globalService.currentUserLiveData.value!!.id!!,
conversation.conversationId!!, favorite
)
}
@ -150,12 +158,30 @@ class ConversationsListViewModel constructor(
})
}
fun loadAvatar() {
val operationUser = globalService.currentUserLiveData.value
operationUser?.let {
viewModelScope.launch {
val url = ApiUtils.getUrlForAvatarWithNameAndPixels(it.baseUrl, it.userId, 512)
val drawable = Coil.get((url)) {
addHeader("Authorization", it.getCredentials())
transformations(CircleCropTransformation())
}
currentUserAvatar.postValue(drawable)
}
}
}
fun loadConversations() {
getConversationsUseCase.invoke(viewModelScope, parametersOf(currentUserLiveData.value), object :
if (conversationsLoadingLock.tryLock()) {
getConversationsUseCase.invoke(viewModelScope, parametersOf(globalService.currentUserLiveData.value), object :
UseCaseResponse<List<Conversation>> {
override suspend fun onSuccess(result: List<Conversation>) {
override suspend fun onSuccess(
result: List<Conversation>) {
networkStateLiveData.postValue(ConversationsListViewNetworkState.LOADED)
val mutableList = result.toMutableList()
val internalUserId = currentUserLiveData.value!!.id
val internalUserId = globalService.currentUserLiveData.value!!.id
mutableList.forEach {
it.internalUserId = internalUserId
}
@ -164,13 +190,17 @@ class ConversationsListViewModel constructor(
internalUserId!!,
mutableList)
messageData = ""
conversationsLoadingLock.unlock()
}
override suspend fun onError(errorModel: ErrorModel?) {
messageData = errorModel?.getErrorMessage()
networkStateLiveData.postValue(ConversationsListViewNetworkState.FAILED)
conversationsLoadingLock.unlock()
}
})
}
}
private suspend fun setConversationUpdateStatus(
@ -178,7 +208,7 @@ class ConversationsListViewModel constructor(
value: Boolean
) {
conversationsRepository.setChangingValueForConversation(
currentUserLiveData.value!!.id!!, conversation
globalService.currentUserLiveData.value!!.id!!, conversation
.conversationId!!, value
)
}

View File

@ -20,8 +20,7 @@
package com.nextcloud.talk.newarch.features.conversationsList
enum class ConversationsListViewState {
INITIAL_LOAD,
enum class ConversationsListViewNetworkState {
LOADING,
LOADED_EMPTY,
LOADED,

View File

@ -22,12 +22,12 @@ package com.nextcloud.talk.newarch.features.conversationsList.di.module
import android.app.Application
import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsRepository
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.domain.usecases.DeleteConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.SetConversationFavoriteValueUseCase
import com.nextcloud.talk.newarch.features.conversationsList.ConversationListViewModelFactory
import com.nextcloud.talk.newarch.services.GlobalService
import org.koin.android.ext.koin.androidApplication
import org.koin.dsl.module
@ -49,11 +49,11 @@ fun createConversationListViewModelFactory(
leaveConversationUseCase: LeaveConversationUseCase,
deleteConversationUseCase: DeleteConversationUseCase,
conversationsRepository: ConversationsRepository,
usersRepository: UsersRepository
globalService: GlobalService
): ConversationListViewModelFactory {
return ConversationListViewModelFactory(
application, getConversationsUseCase,
setConversationFavoriteValueUseCase, leaveConversationUseCase, deleteConversationUseCase,
conversationsRepository, usersRepository
conversationsRepository, globalService
)
}

View File

@ -34,9 +34,9 @@ class ConversationReadOnlyStateConverter {
@TypeConverter
fun fromIntToConversationType(value: Int): ConversationReadOnlyState {
when (value) {
0 -> return CONVERSATION_READ_WRITE
else -> return CONVERSATION_READ_ONLY
return when (value) {
0 -> CONVERSATION_READ_WRITE
else -> CONVERSATION_READ_ONLY
}
}
}

View File

@ -26,11 +26,11 @@ import com.nextcloud.talk.models.json.participants.Participant.ParticipantType
class ParticipantTypeConverter {
@TypeConverter
fun fromParticipantType(participantType: ParticipantType): Int {
return participantType.ordinal
return participantType.value
}
@TypeConverter
fun fromIntToParticipantType(value: Int): ParticipantType {
return ParticipantType.fromValue(value.toLong())
return ParticipantType.fromValue(value)
}
}

View File

@ -96,7 +96,7 @@ data class ConversationEntity(
if (lastActivity != other.lastActivity) return false
if (unreadMessages != other.unreadMessages) return false
if (unreadMention != other.unreadMention) return false
if (lastMessage?.internalMessageId != other.lastMessage?.internalMessageId) return false
if (lastMessage != other.lastMessage) return false
if (objectType != other.objectType) return false
if (notificationLevel != other.notificationLevel) return false
if (conversationReadOnlyState != other.conversationReadOnlyState) return false

View File

@ -30,6 +30,7 @@ import com.nextcloud.talk.models.json.push.PushConfigurationState
import com.nextcloud.talk.newarch.local.models.other.UserStatus
import com.nextcloud.talk.utils.ApiUtils
import kotlinx.android.parcel.Parcelize
import java.util.*
@Parcelize
@Entity(tableName = "users")
@ -63,7 +64,7 @@ data class UserNgEntity(
other as UserNgEntity
if (userId != other.userId) return false
if (username != other.username) return false
//if (username != other.username) return false
if (baseUrl != other.baseUrl) return false
if (token != other.token) return false
if (displayName != other.displayName) return false
@ -77,7 +78,7 @@ data class UserNgEntity(
}
override fun hashCode(): Int {
return userId.hashCode()
return Objects.hash(userId, username)
}
}

View File

@ -1,24 +1,26 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
* * Nextcloud Talk application
* *
* * @author Mario Danic
* * Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com>
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.newarch.utils
package com.nextcloud.talk.newarch.services
import androidx.lifecycle.LiveData
import com.nextcloud.talk.models.json.conversations.Conversation

View File

@ -0,0 +1,36 @@
/*
*
* * Nextcloud Talk application
* *
* * @author Mario Danic
* * Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com>
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.nextcloud.talk.newarch.services
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.newarch.local.models.UserNgEntity
interface GlobalServiceInterface {
enum class OperationStatus {
STATUS_OK,
STATUS_FAILED
}
suspend fun gotConversationInfoForUser(userNgEntity: UserNgEntity, conversation: Conversation?, operationStatus: OperationStatus)
suspend fun joinedConversationForUser(userNgEntity: UserNgEntity, conversation: Conversation?, operationStatus: OperationStatus)
}

View File

@ -1,34 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.newarch.utils
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.newarch.local.models.UserNgEntity
interface GlobalServiceInterface {
enum class OperationStatus {
STATUS_OK,
STATUS_FAILED
}
suspend fun gotConversationInfoForUser(userNgEntity: UserNgEntity, conversation: Conversation?, operationStatus: OperationStatus)
suspend fun joinedConversationForUser(userNgEntity: UserNgEntity, conversation: Conversation?, operationStatus: OperationStatus)
}

View File

@ -20,18 +20,9 @@
package com.nextcloud.talk.newarch.utils
import android.app.ActivityManager
import android.content.Context
import android.content.Context.ACTIVITY_SERVICE
import android.graphics.Bitmap
import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat.getSystemService
import androidx.core.graphics.drawable.toBitmap
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import coil.request.LoadRequest

View File

@ -42,6 +42,7 @@ import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsRepository
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.newarch.services.GlobalService
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import kotlinx.coroutines.GlobalScope

View File

@ -25,7 +25,9 @@ import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.*
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Typeface
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.net.Uri
@ -47,7 +49,6 @@ import androidx.annotation.DrawableRes
import androidx.annotation.XmlRes
import androidx.appcompat.widget.AppCompatDrawableManager
import androidx.core.content.ContextCompat
import androidx.core.graphics.applyCanvas
import androidx.core.graphics.drawable.DrawableCompat
import androidx.emoji.text.EmojiCompat
import coil.Coil
@ -66,7 +67,6 @@ import kotlinx.coroutines.runBlocking
import org.greenrobot.eventbus.EventBus
import java.lang.reflect.InvocationTargetException
import java.util.regex.Pattern
import kotlin.math.min
object DisplayUtils {

View File

@ -40,6 +40,7 @@
android:id="@+id/stateWithMessageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
>
<ImageView

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
~ Nextcloud Talk application
~
~ @author Mario Danic
@ -23,17 +22,19 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- Search, should appear as action button -->
<item android:id="@+id/action_search"
android:title="@string/nc_search"
android:icon="@drawable/ic_search_white_24dp"
app:showAsAction="collapseActionView|always"
<item
android:id="@+id/action_search"
android:animateLayoutChanges="true"
android:icon="@drawable/ic_search_white_24dp"
android:title="@string/nc_search"
android:visible="false"
app:actionViewClass="androidx.appcompat.widget.SearchView"
/>
app:showAsAction="collapseActionView|always" />
<item android:id="@+id/action_settings"
android:title="@string/nc_settings"
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_settings_white_24dp"
android:title="@string/nc_settings"
app:showAsAction="ifRoom" />