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

View File

@ -220,7 +220,7 @@ class MagicIncomingTextMessageViewHolder(incomingView: View) : MessageHolders
message.parentMessage?.let { parentChatMessage -> message.parentMessage?.let { parentChatMessage ->
parentChatMessage.activeUser = message.activeUser parentChatMessage.activeUser = message.activeUser
imageLoader.loadImage(quotedUserAvatar, parentChatMessage.user.avatar, null) imageLoader.loadImage(quotedUserAvatar, parentChatMessage.user.avatar, null)
parentChatMessage.imageUrl?.let{ parentChatMessage.imageUrl?.let {
quotedMessagePreview?.visibility = View.VISIBLE quotedMessagePreview?.visibility = View.VISIBLE
imageLoader.loadImage(quotedMessagePreview, it, null) imageLoader.loadImage(quotedMessagePreview, it, null)
} ?: run { } ?: run {

View File

@ -24,7 +24,6 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.text.Spannable import android.text.Spannable
import android.text.SpannableString import android.text.SpannableString
import android.util.Log
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
@ -146,7 +145,7 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
message.parentMessage?.let { parentChatMessage -> message.parentMessage?.let { parentChatMessage ->
parentChatMessage.activeUser = message.activeUser parentChatMessage.activeUser = message.activeUser
imageLoader.loadImage(quotedUserAvatar, parentChatMessage.user.avatar, null) imageLoader.loadImage(quotedUserAvatar, parentChatMessage.user.avatar, null)
parentChatMessage.imageUrl?.let{ parentChatMessage.imageUrl?.let {
quotedMessagePreview?.visibility = View.VISIBLE quotedMessagePreview?.visibility = View.VISIBLE
imageLoader.loadImage(quotedMessagePreview, it, null) imageLoader.loadImage(quotedMessagePreview, it, null)
} ?: run { } ?: run {

View File

@ -393,7 +393,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
messagesListView?.setAdapter(adapter) messagesListView?.setAdapter(adapter)
adapter?.setLoadMoreListener(this) adapter?.setLoadMoreListener(this)
adapter?.setDateHeadersFormatter { format(it) } adapter?.setDateHeadersFormatter { format(it) }
adapter?.setOnMessageViewLongClickListener { view, message -> onMessageViewLongClick(view, message)} adapter?.setOnMessageViewLongClickListener { view, message -> onMessageViewLongClick(view, message) }
layoutManager = messagesListView?.layoutManager as LinearLayoutManager? layoutManager = messagesListView?.layoutManager as LinearLayoutManager?
@ -931,7 +931,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
messageInput?.setText("") messageInput?.setText("")
val replyMessageId: Long? = view?.findViewById<RelativeLayout>(R.id.quotedChatMessageView)?.tag as Long? val replyMessageId: Long? = view?.findViewById<RelativeLayout>(R.id.quotedChatMessageView)?.tag as Long?
sendMessage(editable, if (view?.findViewById<RelativeLayout>(R.id.quotedChatMessageView)?.visibility == View.VISIBLE) replyMessageId?.toInt() else null ) sendMessage(editable, if (view?.findViewById<RelativeLayout>(R.id.quotedChatMessageView)?.visibility == View.VISIBLE) replyMessageId?.toInt() else null)
cancelReply() cancelReply()
} }
} }
@ -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)?.ellipsize = TextUtils.TruncateAt.END
messageInputView?.findViewById<EmojiTextView>(R.id.quotedMessage)?.text = it.text 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<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 -> conversationUser?.let { currentUser ->
messageInputView?.findViewById<ImageView>(R.id.quotedUserAvatar)?.load(it.user.avatar) { messageInputView?.findViewById<ImageView>(R.id.quotedUserAvatar)?.load(it.user.avatar) {
@ -1420,7 +1421,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
chatMessage.imageUrl?.let{ previewImageUrl -> chatMessage.imageUrl?.let { previewImageUrl ->
messageInputView?.findViewById<ImageView>(R.id.quotedMessageImage)?.visibility = View.VISIBLE messageInputView?.findViewById<ImageView>(R.id.quotedMessageImage)?.visibility = View.VISIBLE
val px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 96f, resources?.displayMetrics) val px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 96f, resources?.displayMetrics)

View File

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

View File

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

View File

@ -26,6 +26,7 @@ import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import lombok.Data import lombok.Data
import org.parceler.Parcel import org.parceler.Parcel
import java.util.*
@Data @Data
@Parcel @Parcel
@ -44,14 +45,15 @@ data class ExternalSignalingServer(
other as ExternalSignalingServer other as ExternalSignalingServer
if (externalSignalingServer != other.externalSignalingServer) return false if (externalSignalingServer != other.externalSignalingServer) return false
if (externalSignalingTicket != other.externalSignalingTicket) return false //if (externalSignalingTicket != other.externalSignalingTicket) return false
return true return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = externalSignalingServer?.hashCode() ?: 0 return Objects.hash(externalSignalingServer)
/*var result = externalSignalingServer?.hashCode() ?: 0
result = 31 * result + (externalSignalingTicket?.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 { override fun equals(o: Any?): Boolean {
if (this === o) return true if (this === o) return true
if (o !is Capabilities) return false if (o !is Capabilities) return false
return (spreedCapability == o.spreedCapability && return (spreedCapability == o.spreedCapability &&
notificationsCapability == o.notificationsCapability && notificationsCapability == o.notificationsCapability &&
themingCapability == o.themingCapability) themingCapability == o.themingCapability)

View File

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

View File

@ -44,6 +44,7 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import lombok.Data; import lombok.Data;
@ -97,13 +98,35 @@ public class ChatMessage implements IMessage, MessageContentType, MessageContent
public boolean replyable; public boolean replyable;
@JsonField(name = "parent") @JsonField(name = "parent")
public ChatMessage parentMessage; public ChatMessage parentMessage;
@JsonIgnore @JsonIgnore
@Ignore @Ignore
List<MessageType> messageTypesToIgnore = Arrays.asList(MessageType.REGULAR_TEXT_MESSAGE, List<MessageType> messageTypesToIgnore = Arrays.asList(MessageType.REGULAR_TEXT_MESSAGE,
MessageType.SYSTEM_MESSAGE, MessageType.SINGLE_LINK_VIDEO_MESSAGE, MessageType.SYSTEM_MESSAGE, MessageType.SINGLE_LINK_VIDEO_MESSAGE,
MessageType.SINGLE_LINK_AUDIO_MESSAGE, MessageType.SINGLE_LINK_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() { private boolean hasFileAttachment() {
if (messageParameters != null && messageParameters.size() > 0) { if (messageParameters != null && messageParameters.size() > 0) {
for (String key : messageParameters.keySet()) { for (String key : messageParameters.keySet()) {

View File

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

View File

@ -27,6 +27,8 @@ import com.nextcloud.talk.models.json.converters.EnumParticipantTypeConverter;
import org.parceler.Parcel; import org.parceler.Parcel;
import java.util.Objects;
import lombok.Data; import lombok.Data;
@Parcel @Parcel
@ -47,8 +49,8 @@ public class Participant {
@JsonField(name = "displayName") @JsonField(name = "displayName")
public String displayName; public String displayName;
@JsonField(name = "lastPing") /*@JsonField(name = "lastPing")
public long lastPing; public long lastPing;*/
@JsonField(name = "sessionId") @JsonField(name = "sessionId")
public String sessionId; public String sessionId;
@ -56,14 +58,35 @@ public class Participant {
@JsonField(name = "conversationId") @JsonField(name = "conversationId")
public long conversationId; public long conversationId;
@JsonField(name = { "inCall", "call" }, typeConverter = EnumParticipantFlagsConverter.class) @JsonField(name = {"inCall", "call"}, typeConverter = EnumParticipantFlagsConverter.class)
public ParticipantFlags participantFlags; public ParticipantFlags participantFlags;
@JsonField(name = "source") @JsonField(name = "source")
public String source; public String source;
public boolean selected; 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 { public enum ParticipantType {
OWNER(1), OWNER(1),
MODERATOR(2), MODERATOR(2),
@ -72,13 +95,13 @@ public class Participant {
USER_FOLLOWING_LINK(5), USER_FOLLOWING_LINK(5),
GUEST_AS_MODERATOR(6); GUEST_AS_MODERATOR(6);
private long value; private Integer value;
ParticipantType(long value) { ParticipantType(Integer value) {
this.value = value; this.value = value;
} }
public static ParticipantType fromValue(long value) { public static ParticipantType fromValue(Integer value) {
if (value == 1) { if (value == 1) {
return OWNER; return OWNER;
} else if (value == 2) { } else if (value == 2) {
@ -96,7 +119,7 @@ public class Participant {
} }
} }
public long getValue() { public Integer getValue() {
return value; return value;
} }
@ -109,13 +132,13 @@ public class Participant {
IN_CALL_WITH_VIDEO(5), IN_CALL_WITH_VIDEO(5),
IN_CALL_WITH_AUDIO_AND_VIDEO(7); IN_CALL_WITH_AUDIO_AND_VIDEO(7);
private long value; private Integer value;
ParticipantFlags(long value) { ParticipantFlags(Integer value) {
this.value = value; this.value = value;
} }
public static ParticipantFlags fromValue(long value) { public static ParticipantFlags fromValue(Integer value) {
if (value == 0) { if (value == 0) {
return NOT_IN_CALL; return NOT_IN_CALL;
} else if (value == 1) { } else if (value == 1) {
@ -131,7 +154,7 @@ public class Participant {
} }
} }
public long getValue() { public Integer getValue() {
return value; 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.repository.online.NextcloudTalkRepository
import com.nextcloud.talk.newarch.domain.usecases.* import com.nextcloud.talk.newarch.domain.usecases.*
import com.nextcloud.talk.newarch.features.chat.ChatViewModelFactory 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 import org.koin.dsl.module
val ConversationsModule = 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.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.domain.usecases.GetConversationUseCase import com.nextcloud.talk.newarch.domain.usecases.GetConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.JoinConversationUseCase 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 com.nextcloud.talk.newarch.utils.ShortcutService
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.koin.dsl.module 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.ExitConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.JoinConversationUseCase import com.nextcloud.talk.newarch.domain.usecases.JoinConversationUseCase
import com.nextcloud.talk.newarch.local.models.UserNgEntity import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.utils.GlobalService import com.nextcloud.talk.newarch.services.GlobalService
import com.nextcloud.talk.newarch.utils.GlobalServiceInterface import com.nextcloud.talk.newarch.services.GlobalServiceInterface
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ChatViewModel constructor(application: Application, 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.repository.offline.MessagesRepository
import com.nextcloud.talk.newarch.domain.usecases.ExitConversationUseCase import com.nextcloud.talk.newarch.domain.usecases.ExitConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.JoinConversationUseCase 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( class ChatViewModelFactory constructor(
private val application: Application, private val application: Application,

View File

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

View File

@ -23,7 +23,6 @@ package com.nextcloud.talk.newarch.features.conversationsList
import android.app.SearchManager import android.app.SearchManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.InputType import android.text.InputType
@ -32,13 +31,10 @@ import android.view.inputmethod.EditorInfo
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.SearchView.OnQueryTextListener import androidx.appcompat.widget.SearchView.OnQueryTextListener
import androidx.core.view.MenuItemCompat import androidx.core.view.MenuItemCompat
import androidx.lifecycle.MutableLiveData import androidx.core.view.isVisible
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.recyclerview.widget.RecyclerView.ViewHolder
import butterknife.OnClick 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.LayoutMode.WRAP_CONTENT
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.bottomsheets.BottomSheet 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.controllers.bottomsheet.items.listItemsWithImage
import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseView 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.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.ConductorRemapping
import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.ShareUtils import com.nextcloud.talk.utils.ShareUtils
@ -81,8 +75,6 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
private lateinit var viewModel: ConversationsListViewModel private lateinit var viewModel: ConversationsListViewModel
val factory: ConversationListViewModelFactory by inject() 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) private val recyclerViewAdapter = FlexibleAdapter(mutableListOf(), null, false)
@ -109,42 +101,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
} }
settingsItem = menu.findItem(R.id.action_settings) settingsItem = menu.findItem(R.id.action_settings)
} viewModel.loadAvatar()
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)
}
}
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -186,10 +143,6 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
searchView!!.setOnQueryTextListener(this) searchView!!.setOnQueryTextListener(this)
} }
override fun onRestoreViewState(view: View, savedViewState: Bundle) {
super.onRestoreViewState(view, savedViewState)
viewModel.loadConversations()
}
override fun onQueryTextSubmit(query: String?): Boolean { override fun onQueryTextSubmit(query: String?): Boolean {
if (!viewModel.searchQuery.value.equals(query)) { if (!viewModel.searchQuery.value.equals(query)) {
@ -212,26 +165,32 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
viewModel = viewModelProvider(factory).get(ConversationsListViewModel::class.java) viewModel = viewModelProvider(factory).get(ConversationsListViewModel::class.java)
viewModel.apply { viewModel.apply {
currentUserLiveData.observe(this@ConversationsListView, Observer { value -> currentUserAvatar.observe(this@ConversationsListView, Observer { value ->
loadAvatar() settingsItem?.icon = value
}) })
conversationsLiveData.observe(this@ConversationsListView, Observer { conversationsLiveData.observe(this@ConversationsListView, Observer {
if (it.isEmpty()) { val isListEmpty = it.isNullOrEmpty()
if (viewState.value != LOADED_EMPTY) {
viewState.value = LOADED_EMPTY if (isListEmpty) {
} view?.stateWithMessageView?.errorStateTextView?.text =
} else { resources?.getText(R.string.nc_conversations_empty)
if (viewState.value != LOADED) { view?.stateWithMessageView?.errorStateImageView?.setImageResource(drawable.ic_logo)
viewState.value = LOADED
}
} }
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>() val newConversations = mutableListOf<ConversationItem>()
for (conversation in it) { for (conversation in it) {
newConversations.add( newConversations.add(
ConversationItem( ConversationItem(
conversation, viewModel.currentUserLiveData.value!!, conversation, globalService.currentUserLiveData.value!!,
activity!! activity!!
) )
) )
@ -241,6 +200,46 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
newConversations as newConversations as
List<IFlexible<ViewHolder>>?, false 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 { 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) return super.onCreateView(inflater, container)
@ -324,7 +264,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
@OnClick(R.id.stateWithMessageView) @OnClick(R.id.stateWithMessageView)
fun onStateWithMessageViewClick() { fun onStateWithMessageViewClick() {
if (viewState.value!! == LOADED_EMPTY) { if (view?.floatingActionButton?.isVisible == true) {
openNewConversationScreen() openNewConversationScreen()
} }
} }
@ -392,7 +332,7 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
) )
} }
if (conversation.canLeave(viewModel.currentUserLiveData.value!!)) { if (conversation.canLeave(viewModel.globalService.currentUserLiveData.value!!)) {
items.add( items.add(
BasicListItemWithImage( BasicListItemWithImage(
drawable.ic_exit_to_app_black_24dp, context.getString 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( items.add(
BasicListItemWithImage( BasicListItemWithImage(
drawable.ic_delete_grey600_24dp, context.getString( drawable.ic_delete_grey600_24dp, context.getString(
@ -418,6 +358,11 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
return resources?.getString(R.string.nc_app_name) 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) { override fun onAttach(view: View) {
super.onAttach(view) super.onAttach(view)
view.recyclerView.initRecyclerView( view.recyclerView.initRecyclerView(
@ -432,7 +377,6 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
view.swipeRefreshLayoutView.isRefreshing = false view.swipeRefreshLayoutView.isRefreshing = false
viewModel.loadConversations() viewModel.loadConversations()
} }
view.swipeRefreshLayoutView.setColorSchemeResources(R.color.colorPrimary) view.swipeRefreshLayoutView.setColorSchemeResources(R.color.colorPrimary)
view.fast_scroller.setBubbleTextCreator { position -> view.fast_scroller.setBubbleTextCreator { position ->
@ -514,15 +458,16 @@ class ConversationsListView : BaseView(), OnQueryTextListener,
): Boolean { ): Boolean {
val clickedItem = recyclerViewAdapter.getItem(position) val clickedItem = recyclerViewAdapter.getItem(position)
if (clickedItem != null) { if (clickedItem != null) {
val conversation = (clickedItem as ConversationItem).model val conversationItem = clickedItem as ConversationItem
val conversation = conversationItem.model
val bundle = Bundle() 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_TOKEN, conversation.token)
bundle.putString(BundleKeys.KEY_ROOM_ID, conversation.conversationId) bundle.putString(BundleKeys.KEY_ROOM_ID, conversation.conversationId)
bundle.putParcelable(BundleKeys.KEY_ACTIVE_CONVERSATION, Parcels.wrap(conversation)) bundle.putParcelable(BundleKeys.KEY_ACTIVE_CONVERSATION, Parcels.wrap(conversation))
ConductorRemapping.remapChatController( ConductorRemapping.remapChatController(
router, viewModel.currentUserLiveData.value!!.id!!, conversation.token!!, router, conversationItem.user.id!!, conversation.token!!,
bundle, false bundle, false
) )
} }

View File

@ -21,30 +21,31 @@
package com.nextcloud.talk.newarch.features.conversationsList package com.nextcloud.talk.newarch.features.conversationsList
import android.app.Application import android.app.Application
import android.content.Intent import android.graphics.drawable.Drawable
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import androidx.lifecycle.viewModelScope 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
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.conversations.Conversation
import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel
import com.nextcloud.talk.newarch.data.model.ErrorModel 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.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.DeleteConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.SetConversationFavoriteValueUseCase import com.nextcloud.talk.newarch.domain.usecases.SetConversationFavoriteValueUseCase
import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse
import com.nextcloud.talk.newarch.local.models.UserNgEntity import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.utils.ShareUtils import com.nextcloud.talk.newarch.services.GlobalService
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import java.util.concurrent.locks.ReentrantLock
class ConversationsListViewModel constructor( class ConversationsListViewModel constructor(
application: Application, application: Application,
@ -53,14 +54,21 @@ class ConversationsListViewModel constructor(
private val leaveConversationUseCase: LeaveConversationUseCase, private val leaveConversationUseCase: LeaveConversationUseCase,
private val deleteConversationUseCase: DeleteConversationUseCase, private val deleteConversationUseCase: DeleteConversationUseCase,
private val conversationsRepository: ConversationsRepository, private val conversationsRepository: ConversationsRepository,
usersRepository: UsersRepository val globalService: GlobalService
) : BaseViewModel<ConversationsListView>(application) { ) : BaseViewModel<ConversationsListView>(application) {
private val conversationsLoadingLock = ReentrantLock()
var messageData: String? = null var messageData: String? = null
val searchQuery = MutableLiveData<String>() val searchQuery = MutableLiveData<String>()
val currentUserLiveData: LiveData<UserNgEntity> = usersRepository.getActiveUserLiveData() val networkStateLiveData: MutableLiveData<ConversationsListViewNetworkState> = MutableLiveData(ConversationsListViewNetworkState.LOADING)
val conversationsLiveData = Transformations.switchMap(currentUserLiveData) { 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() loadConversations()
loadAvatar()
conversationsRepository.getConversationsForUser(it.id!!) conversationsRepository.getConversationsForUser(it.id!!)
} }
@ -70,13 +78,13 @@ class ConversationsListViewModel constructor(
} }
leaveConversationUseCase.invoke(viewModelScope, parametersOf( leaveConversationUseCase.invoke(viewModelScope, parametersOf(
currentUserLiveData.value, globalService.currentUserLiveData.value,
conversation conversation
), ),
object : UseCaseResponse<GenericOverall> { object : UseCaseResponse<GenericOverall> {
override suspend fun onSuccess(result: GenericOverall) { override suspend fun onSuccess(result: GenericOverall) {
conversationsRepository.deleteConversation( conversationsRepository.deleteConversation(
currentUserLiveData.value!!.id!!, conversation globalService.currentUserLiveData.value!!.id!!, conversation
.conversationId!! .conversationId!!
) )
} }
@ -99,13 +107,13 @@ class ConversationsListViewModel constructor(
} }
deleteConversationUseCase.invoke(viewModelScope, parametersOf( deleteConversationUseCase.invoke(viewModelScope, parametersOf(
currentUserLiveData.value, globalService.currentUserLiveData.value,
conversation conversation
), ),
object : UseCaseResponse<GenericOverall> { object : UseCaseResponse<GenericOverall> {
override suspend fun onSuccess(result: GenericOverall) { override suspend fun onSuccess(result: GenericOverall) {
conversationsRepository.deleteConversation( conversationsRepository.deleteConversation(
currentUserLiveData.value!!.id!!, conversation globalService.currentUserLiveData.value!!.id!!, conversation
.conversationId!! .conversationId!!
) )
} }
@ -129,14 +137,14 @@ class ConversationsListViewModel constructor(
} }
setConversationFavoriteValueUseCase.invoke(viewModelScope, parametersOf( setConversationFavoriteValueUseCase.invoke(viewModelScope, parametersOf(
currentUserLiveData.value, globalService.currentUserLiveData.value,
conversation, conversation,
favorite favorite
), ),
object : UseCaseResponse<GenericOverall> { object : UseCaseResponse<GenericOverall> {
override suspend fun onSuccess(result: GenericOverall) { override suspend fun onSuccess(result: GenericOverall) {
conversationsRepository.setFavoriteValueForConversation( conversationsRepository.setFavoriteValueForConversation(
currentUserLiveData.value!!.id!!, globalService.currentUserLiveData.value!!.id!!,
conversation.conversationId!!, favorite conversation.conversationId!!, favorite
) )
} }
@ -150,26 +158,48 @@ 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() { fun loadConversations() {
getConversationsUseCase.invoke(viewModelScope, parametersOf(currentUserLiveData.value), object : if (conversationsLoadingLock.tryLock()) {
UseCaseResponse<List<Conversation>> { getConversationsUseCase.invoke(viewModelScope, parametersOf(globalService.currentUserLiveData.value), object :
override suspend fun onSuccess(result: List<Conversation>) { UseCaseResponse<List<Conversation>> {
val mutableList = result.toMutableList() override suspend fun onSuccess(
val internalUserId = currentUserLiveData.value!!.id result: List<Conversation>) {
mutableList.forEach { networkStateLiveData.postValue(ConversationsListViewNetworkState.LOADED)
it.internalUserId = internalUserId val mutableList = result.toMutableList()
val internalUserId = globalService.currentUserLiveData.value!!.id
mutableList.forEach {
it.internalUserId = internalUserId
}
conversationsRepository.saveConversationsForUser(
internalUserId!!,
mutableList)
messageData = ""
conversationsLoadingLock.unlock()
} }
conversationsRepository.saveConversationsForUser( override suspend fun onError(errorModel: ErrorModel?) {
internalUserId!!, messageData = errorModel?.getErrorMessage()
mutableList) networkStateLiveData.postValue(ConversationsListViewNetworkState.FAILED)
messageData = "" conversationsLoadingLock.unlock()
} }
})
override suspend fun onError(errorModel: ErrorModel?) { }
messageData = errorModel?.getErrorMessage()
}
})
} }
@ -178,7 +208,7 @@ class ConversationsListViewModel constructor(
value: Boolean value: Boolean
) { ) {
conversationsRepository.setChangingValueForConversation( conversationsRepository.setChangingValueForConversation(
currentUserLiveData.value!!.id!!, conversation globalService.currentUserLiveData.value!!.id!!, conversation
.conversationId!!, value .conversationId!!, value
) )
} }

View File

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

View File

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

View File

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

View File

@ -26,7 +26,7 @@ import androidx.room.TypeConverter
import com.bluelinelabs.logansquare.LoganSquare import com.bluelinelabs.logansquare.LoganSquare
import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.models.json.participants.Participant
class ParticipantMapConverter { class ParticipantMapConverter {
@TypeConverter @TypeConverter
fun fromMapToString(map: HashMap<String, Participant>?): String? { fun fromMapToString(map: HashMap<String, Participant>?): String? {
if (map == null) { if (map == null) {

View File

@ -26,11 +26,11 @@ import com.nextcloud.talk.models.json.participants.Participant.ParticipantType
class ParticipantTypeConverter { class ParticipantTypeConverter {
@TypeConverter @TypeConverter
fun fromParticipantType(participantType: ParticipantType): Int { fun fromParticipantType(participantType: ParticipantType): Int {
return participantType.ordinal return participantType.value
} }
@TypeConverter @TypeConverter
fun fromIntToParticipantType(value: Int): ParticipantType { 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 (lastActivity != other.lastActivity) return false
if (unreadMessages != other.unreadMessages) return false if (unreadMessages != other.unreadMessages) return false
if (unreadMention != other.unreadMention) 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 (objectType != other.objectType) return false
if (notificationLevel != other.notificationLevel) return false if (notificationLevel != other.notificationLevel) return false
if (conversationReadOnlyState != other.conversationReadOnlyState) 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.newarch.local.models.other.UserStatus
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import java.util.*
@Parcelize @Parcelize
@Entity(tableName = "users") @Entity(tableName = "users")
@ -63,7 +64,7 @@ data class UserNgEntity(
other as UserNgEntity other as UserNgEntity
if (userId != other.userId) return false 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 (baseUrl != other.baseUrl) return false
if (token != other.token) return false if (token != other.token) return false
if (displayName != other.displayName) return false if (displayName != other.displayName) return false
@ -77,7 +78,7 @@ data class UserNgEntity(
} }
override fun hashCode(): Int { override fun hashCode(): Int {
return userId.hashCode() return Objects.hash(userId, username)
} }
} }

View File

@ -1,24 +1,26 @@
/* /*
* Nextcloud Talk application
* *
* @author Mario Danic * * Nextcloud Talk application
* Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com> * *
* * @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 androidx.lifecycle.LiveData
import com.nextcloud.talk.models.json.conversations.Conversation 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 package com.nextcloud.talk.newarch.utils
import android.app.ActivityManager
import android.content.Context 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.Drawable
import android.graphics.drawable.LayerDrawable 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 androidx.lifecycle.LifecycleOwner
import coil.ImageLoader import coil.ImageLoader
import coil.request.LoadRequest import coil.request.LoadRequest
@ -73,7 +64,7 @@ class Images {
} }
} }
} }
// returns null if it's one-to-one that you need to fetch yourself // returns null if it's one-to-one that you need to fetch yourself
fun getImageForConversation(context: Context, conversation: Conversation): Drawable? { fun getImageForConversation(context: Context, conversation: Conversation): Drawable? {
conversation.objectType?.let { objectType -> conversation.objectType?.let { objectType ->

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.domain.repository.offline.ConversationsRepository
import com.nextcloud.talk.newarch.local.models.UserNgEntity import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.getCredentials 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.ApiUtils
import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.bundle.BundleKeys
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope

View File

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

View File

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

View File

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