delete dialogs
delete messages (+ for all & mark as spam)
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
package com.meloda.fast.api
|
||||
|
||||
object ApiExtensions {
|
||||
|
||||
val Boolean.intString get() = (if (this) 1 else 0).toString()
|
||||
|
||||
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import android.text.SpannableString
|
||||
import android.text.style.StyleSpan
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.meloda.fast.R
|
||||
import com.meloda.fast.api.model.VkConversation
|
||||
import com.meloda.fast.api.model.VkGroup
|
||||
import com.meloda.fast.api.model.VkMessage
|
||||
import com.meloda.fast.api.model.VkUser
|
||||
@@ -16,6 +17,64 @@ import com.meloda.fast.api.model.base.attachments.BaseVkAttachmentItem
|
||||
|
||||
object VkUtils {
|
||||
|
||||
fun getMessageUser(message: VkMessage, profiles: Map<Int, VkUser>): VkUser? {
|
||||
return (if (!message.isUser()) null
|
||||
else profiles[message.fromId]).also { message.user.value = it }
|
||||
}
|
||||
|
||||
fun getMessageGroup(message: VkMessage, groups: Map<Int, VkGroup>): VkGroup? {
|
||||
return (if (!message.isGroup()) null
|
||||
else groups[message.fromId]).also { message.group.value = it }
|
||||
}
|
||||
|
||||
fun getMessageAvatar(
|
||||
message: VkMessage,
|
||||
messageUser: VkUser?,
|
||||
messageGroup: VkGroup?
|
||||
): String? {
|
||||
return when {
|
||||
message.isUser() -> messageUser?.photo200
|
||||
message.isGroup() -> messageGroup?.photo200
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun getMessageTitle(
|
||||
message: VkMessage,
|
||||
messageUser: VkUser?,
|
||||
messageGroup: VkGroup?
|
||||
): String? {
|
||||
return when {
|
||||
message.isUser() -> messageUser?.fullName
|
||||
message.isGroup() -> messageGroup?.name
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun getConversationUser(conversation: VkConversation, profiles: Map<Int, VkUser>): VkUser? {
|
||||
return (if (!conversation.isUser()) null
|
||||
else profiles[conversation.id]).also { conversation.user.value = it }
|
||||
}
|
||||
|
||||
fun getConversationGroup(conversation: VkConversation, groups: Map<Int, VkGroup>): VkGroup? {
|
||||
return (if (!conversation.isGroup()) null
|
||||
else groups[conversation.id]).also { conversation.group.value = it }
|
||||
}
|
||||
|
||||
fun getConversationAvatar(
|
||||
conversation: VkConversation,
|
||||
conversationUser: VkUser?,
|
||||
conversationGroup: VkGroup?
|
||||
): String? {
|
||||
return when {
|
||||
conversation.ownerId == VKConstants.FAST_GROUP_ID -> null
|
||||
conversation.isUser() -> conversationUser?.photo200
|
||||
conversation.isGroup() -> conversationGroup?.photo200
|
||||
conversation.isChat() -> conversation.photo200
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun prepareMessageText(text: String, forConversations: Boolean? = null): String {
|
||||
return text.apply {
|
||||
if (forConversations == true) replace("\n", "")
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.meloda.fast.api.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Entity(tableName = "conversations")
|
||||
@@ -34,6 +37,14 @@ data class VkConversation(
|
||||
var lastMessage: VkMessage? = null,
|
||||
) : Parcelable {
|
||||
|
||||
@Ignore
|
||||
@IgnoredOnParcel
|
||||
val user = MutableLiveData<VkUser?>()
|
||||
|
||||
@Ignore
|
||||
@IgnoredOnParcel
|
||||
val group = MutableLiveData<VkGroup?>()
|
||||
|
||||
fun isChat() = type == "chat"
|
||||
fun isUser() = type == "user"
|
||||
fun isGroup() = type == "group"
|
||||
|
||||
@@ -24,3 +24,8 @@ data class ConversationsGetRequest(
|
||||
startMessageId?.let { this["start_message_id"] = it.toString() }
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class ConversationsDeleteRequest(val peerId: Int) : Parcelable {
|
||||
val map get() = mapOf("peer_id" to peerId.toString())
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.meloda.fast.api.model.request
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.meloda.fast.api.ApiExtensions.intString
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@@ -20,9 +21,9 @@ data class MessagesGetHistoryRequest(
|
||||
).apply {
|
||||
count?.let { this["count"] = it.toString() }
|
||||
offset?.let { this["offset"] = it.toString() }
|
||||
extended?.let { this["extended"] = (if (it) 1 else 0).toString() }
|
||||
extended?.let { this["extended"] = it.intString }
|
||||
startMessageId?.let { this["start_message_id"] = it.toString() }
|
||||
rev?.let { this["rev"] = (if (it) 1 else 0).toString() }
|
||||
rev?.let { this["rev"] = it.intString }
|
||||
fields?.let { this["fields"] = it }
|
||||
}
|
||||
|
||||
@@ -51,8 +52,8 @@ data class MessagesSendRequest(
|
||||
lon?.let { this["lon"] = it.toString() }
|
||||
replyTo?.let { this["reply_to"] = it.toString() }
|
||||
stickerId?.let { this["sticker_id"] = it.toString() }
|
||||
disableMentions?.let { this["disable_mentions"] = (if (it) 1 else 0).toString() }
|
||||
dontParseLinks?.let { this["dont_parse_links"] = (if (it) 1 else 0).toString() }
|
||||
disableMentions?.let { this["disable_mentions"] = it.intString }
|
||||
dontParseLinks?.let { this["dont_parse_links"] = it.intString }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,11 +66,25 @@ data class MessagesMarkAsImportantRequest(
|
||||
val map
|
||||
get() = mutableMapOf(
|
||||
"message_ids" to messagesIds.joinToString { it.toString() },
|
||||
"important" to (if (important) 1 else 0).toString()
|
||||
"important" to important.intString
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class MessagesGetLongPollServerRequest(
|
||||
val needPts: Boolean,
|
||||
val version: Int
|
||||
) : Parcelable {
|
||||
|
||||
val map
|
||||
get() = mutableMapOf(
|
||||
"need_pts" to needPts.intString,
|
||||
"version" to version.toString()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Parcelize
|
||||
data class MessagesPinMessageRequest(
|
||||
val peerId: Int,
|
||||
@@ -93,14 +108,27 @@ data class MessagesUnPinMessageRequest(val peerId: Int) : Parcelable {
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class MessagesGetLongPollServerRequest(
|
||||
val needPts: Boolean,
|
||||
val version: Int
|
||||
data class MessagesDeleteRequest(
|
||||
val peerId: Int,
|
||||
val messagesIds: List<Int>? = null,
|
||||
val conversationsMessagesIds: List<Int>? = null,
|
||||
val isSpam: Boolean? = null,
|
||||
val deleteForAll: Boolean? = null
|
||||
) : Parcelable {
|
||||
|
||||
val map
|
||||
get() = mutableMapOf(
|
||||
"need_pts" to (if (needPts) 1 else 0).toString(),
|
||||
"version" to version.toString()
|
||||
)
|
||||
"peer_id" to peerId.toString()
|
||||
).apply {
|
||||
isSpam?.let { this["spam"] = it.intString }
|
||||
deleteForAll?.let { this["delete_for_all"] = it.intString }
|
||||
messagesIds?.let {
|
||||
this["message_ids"] = it.joinToString { id -> id.toString() }
|
||||
}
|
||||
|
||||
conversationsMessagesIds?.let {
|
||||
this["conversation_message_ids"] = it.joinToString { id -> id.toString() }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,6 +12,7 @@ object VkUrls {
|
||||
|
||||
object Conversations {
|
||||
const val Get = "$API/messages.getConversations"
|
||||
const val Delete = "$API/messages.deleteConversation"
|
||||
}
|
||||
|
||||
object Users {
|
||||
@@ -22,10 +23,11 @@ object VkUrls {
|
||||
const val GetHistory = "$API/messages.getHistory"
|
||||
const val Send = "$API/messages.send"
|
||||
const val MarkAsImportant = "$API/messages.markAsImportant"
|
||||
const val Pin = "$API/messages.pin"
|
||||
const val Unpin = "$API/messages.unpin"
|
||||
const val GetLongPollServer = "$API/messages.getLongPollServer"
|
||||
const val GetLongPollHistory = "$API/messages.getLongPollHistory"
|
||||
const val Pin = "$API/messages.pin"
|
||||
const val Unpin = "$API/messages.unpin"
|
||||
const val Delete = "$API/messages.delete"
|
||||
}
|
||||
|
||||
|
||||
|
||||
+6
-3
@@ -1,8 +1,9 @@
|
||||
package com.meloda.fast.api.network.datasource
|
||||
|
||||
import com.meloda.fast.api.model.VkConversation
|
||||
import com.meloda.fast.api.network.repo.ConversationsRepo
|
||||
import com.meloda.fast.api.model.request.ConversationsDeleteRequest
|
||||
import com.meloda.fast.api.model.request.ConversationsGetRequest
|
||||
import com.meloda.fast.api.network.repo.ConversationsRepo
|
||||
import com.meloda.fast.database.dao.ConversationsDao
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -11,8 +12,10 @@ class ConversationsDataSource @Inject constructor(
|
||||
private val dao: ConversationsDao
|
||||
) {
|
||||
|
||||
suspend fun getAllChats(params: ConversationsGetRequest) = repo.getAllChats(params.map)
|
||||
suspend fun get(params: ConversationsGetRequest) = repo.get(params.map)
|
||||
|
||||
suspend fun storeConversations(conversations: List<VkConversation>) = dao.insert(conversations)
|
||||
suspend fun delete(params: ConversationsDeleteRequest) = repo.delete(params.map)
|
||||
|
||||
suspend fun store(conversations: List<VkConversation>) = dao.insert(conversations)
|
||||
|
||||
}
|
||||
@@ -29,6 +29,9 @@ class MessagesDataSource @Inject constructor(
|
||||
suspend fun unpin(params: MessagesUnPinMessageRequest) =
|
||||
repo.unpin(params.map)
|
||||
|
||||
suspend fun delete(params: MessagesDeleteRequest) =
|
||||
repo.delete(params.map)
|
||||
|
||||
suspend fun store(messages: List<VkMessage>) = dao.insert(messages)
|
||||
|
||||
suspend fun getCached(peerId: Int) = dao.getByPeerId(peerId)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.meloda.fast.api.network.repo
|
||||
|
||||
import com.meloda.fast.api.base.ApiResponse
|
||||
import com.meloda.fast.api.model.response.ConversationsGetResponse
|
||||
import com.meloda.fast.api.network.Answer
|
||||
import com.meloda.fast.api.network.VkUrls
|
||||
import com.meloda.fast.api.model.response.ConversationsGetResponse
|
||||
import retrofit2.http.FieldMap
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.POST
|
||||
@@ -12,6 +12,10 @@ interface ConversationsRepo {
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST(VkUrls.Conversations.Get)
|
||||
suspend fun getAllChats(@FieldMap params: Map<String, String>): Answer<ApiResponse<ConversationsGetResponse>>
|
||||
suspend fun get(@FieldMap params: Map<String, String>): Answer<ApiResponse<ConversationsGetResponse>>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST(VkUrls.Conversations.Delete)
|
||||
suspend fun delete(@FieldMap params: Map<String, String>): Answer<ApiResponse<Any>>
|
||||
|
||||
}
|
||||
@@ -36,4 +36,8 @@ interface MessagesRepo {
|
||||
@POST(VkUrls.Messages.Unpin)
|
||||
suspend fun unpin(@FieldMap params: Map<String, String>): Answer<ApiResponse<Any>>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST(VkUrls.Messages.Delete)
|
||||
suspend fun delete(@FieldMap params: Map<String, String>): Answer<ApiResponse<Any>>
|
||||
|
||||
}
|
||||
@@ -75,29 +75,17 @@ class ConversationsAdapter constructor(
|
||||
return
|
||||
}
|
||||
|
||||
val chatUser: VkUser? = if (conversation.isUser()) {
|
||||
profiles[conversation.id]
|
||||
} else null
|
||||
val conversationUser = VkUtils.getConversationUser(conversation, profiles)
|
||||
val conversationGroup = VkUtils.getConversationGroup(conversation, groups)
|
||||
|
||||
val messageUser: VkUser? = if (message.isUser()) {
|
||||
profiles[message.fromId]
|
||||
} else null
|
||||
val messageUser = VkUtils.getMessageUser(message, profiles)
|
||||
val messageGroup = VkUtils.getMessageGroup(message, groups)
|
||||
|
||||
val chatGroup: VkGroup? = if (conversation.isGroup()) {
|
||||
groups[conversation.id]
|
||||
} else null
|
||||
|
||||
val messageGroup: VkGroup? = if (message.isGroup()) {
|
||||
groups[message.fromId]
|
||||
} else null
|
||||
|
||||
val avatar = when {
|
||||
conversation.ownerId == VKConstants.FAST_GROUP_ID -> null
|
||||
conversation.isUser() && chatUser != null && !chatUser.photo200.isNullOrBlank() -> chatUser.photo200
|
||||
conversation.isGroup() && chatGroup != null && !chatGroup.photo200.isNullOrBlank() -> chatGroup.photo200
|
||||
conversation.isChat() && !conversation.photo200.isNullOrBlank() -> conversation.photo200
|
||||
else -> null
|
||||
}
|
||||
val avatar = VkUtils.getConversationAvatar(
|
||||
conversation = conversation,
|
||||
conversationUser = conversationUser,
|
||||
conversationGroup = conversationGroup
|
||||
)
|
||||
|
||||
binding.avatar.isVisible = avatar != null
|
||||
|
||||
@@ -136,7 +124,7 @@ class ConversationsAdapter constructor(
|
||||
}
|
||||
}
|
||||
|
||||
binding.online.isVisible = chatUser?.online == true
|
||||
binding.online.isVisible = conversationUser?.online == true
|
||||
|
||||
binding.pin.isVisible = conversation.isPinned
|
||||
|
||||
@@ -210,7 +198,8 @@ class ConversationsAdapter constructor(
|
||||
binding.message.text = spanMessage
|
||||
|
||||
binding.title.text =
|
||||
getItem(position).title ?: chatUser?.toString() ?: chatGroup?.name ?: "..."
|
||||
getItem(position).title ?: conversationUser?.toString() ?: conversationGroup?.name
|
||||
?: "..."
|
||||
|
||||
binding.date.text = TimeUtils.getLocalizedTime(context, message.date * 1000L)
|
||||
|
||||
@@ -232,6 +221,18 @@ class ConversationsAdapter constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun removeConversation(conversationId: Int): Int? {
|
||||
for (i in values.indices) {
|
||||
val conversation = values[i]
|
||||
if (conversation.id == conversationId) {
|
||||
values.removeAt(i)
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val COMPARATOR = object : DiffUtil.ItemCallback<VkConversation>() {
|
||||
override fun areItemsTheSame(
|
||||
|
||||
@@ -188,9 +188,11 @@ class ConversationsFragment :
|
||||
override fun onEvent(event: VkEvent) {
|
||||
super.onEvent(event)
|
||||
when (event) {
|
||||
is ConversationsLoaded -> refreshConversations(event)
|
||||
is StartProgressEvent -> onProgressStarted()
|
||||
is StopProgressEvent -> onProgressStopped()
|
||||
|
||||
is ConversationsLoaded -> refreshConversations(event)
|
||||
is ConversationsDelete -> deleteConversation(event.peerId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,8 +265,40 @@ class ConversationsFragment :
|
||||
}
|
||||
|
||||
private fun onItemLongClick(position: Int): Boolean {
|
||||
binding.createChat.performClick()
|
||||
showOptionsDialog(position)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun showOptionsDialog(position: Int) {
|
||||
val conversation = adapter[position]
|
||||
|
||||
val delete = getString(R.string.conversation_context_action_delete)
|
||||
|
||||
val params = mutableListOf(delete)
|
||||
|
||||
val arrayParams = params.toTypedArray()
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setItems(arrayParams) { _, which ->
|
||||
when (params[which]) {
|
||||
delete -> showDeleteConversationDialog(conversation.id)
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
|
||||
private fun showDeleteConversationDialog(conversationId: Int) {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.confirm_delete_conversation)
|
||||
.setPositiveButton(R.string.action_delete) { _, _ ->
|
||||
viewModel.deleteConversation(conversationId)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun deleteConversation(conversationId: Int) {
|
||||
val index = adapter.removeConversation(conversationId) ?: return
|
||||
adapter.notifyItemRemoved(index)
|
||||
}
|
||||
|
||||
}
|
||||
+16
-5
@@ -6,6 +6,7 @@ import com.meloda.fast.api.VKConstants
|
||||
import com.meloda.fast.api.model.VkConversation
|
||||
import com.meloda.fast.api.model.VkGroup
|
||||
import com.meloda.fast.api.model.VkUser
|
||||
import com.meloda.fast.api.model.request.ConversationsDeleteRequest
|
||||
import com.meloda.fast.api.model.request.ConversationsGetRequest
|
||||
import com.meloda.fast.api.model.request.UsersGetRequest
|
||||
import com.meloda.fast.api.network.datasource.ConversationsDataSource
|
||||
@@ -20,15 +21,15 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ConversationsViewModel @Inject constructor(
|
||||
private val dataSource: ConversationsDataSource,
|
||||
private val usersDataSource: UsersDataSource
|
||||
private val conversations: ConversationsDataSource,
|
||||
private val users: UsersDataSource
|
||||
) : BaseViewModel() {
|
||||
|
||||
fun loadConversations(
|
||||
offset: Int? = null
|
||||
) = viewModelScope.launch(Dispatchers.Default) {
|
||||
makeJob({
|
||||
dataSource.getAllChats(
|
||||
conversations.get(
|
||||
ConversationsGetRequest(
|
||||
count = 30,
|
||||
extended = true,
|
||||
@@ -69,16 +70,24 @@ class ConversationsViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun loadProfileUser() = viewModelScope.launch {
|
||||
makeJob({ usersDataSource.getById(UsersGetRequest(fields = VKConstants.USER_FIELDS)) },
|
||||
makeJob({ users.getById(UsersGetRequest(fields = VKConstants.USER_FIELDS)) },
|
||||
onAnswer = {
|
||||
it.response?.let { r ->
|
||||
val users = r.map { u -> u.asVkUser() }
|
||||
usersDataSource.storeUsers(users)
|
||||
this@ConversationsViewModel.users.storeUsers(users)
|
||||
|
||||
UserConfig.vkUser.value = users[0]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun deleteConversation(peerId: Int) = viewModelScope.launch {
|
||||
makeJob({
|
||||
conversations.delete(
|
||||
ConversationsDeleteRequest(peerId)
|
||||
)
|
||||
}, onAnswer = { sendEvent(ConversationsDelete(peerId)) })
|
||||
}
|
||||
}
|
||||
|
||||
data class ConversationsLoaded(
|
||||
@@ -89,3 +98,5 @@ data class ConversationsLoaded(
|
||||
val profiles: HashMap<Int, VkUser>,
|
||||
val groups: HashMap<Int, VkGroup>
|
||||
) : VkEvent()
|
||||
|
||||
data class ConversationsDelete(val peerId: Int) : VkEvent()
|
||||
@@ -5,7 +5,6 @@ import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.widget.LinearLayoutCompat
|
||||
import androidx.core.view.isVisible
|
||||
@@ -17,7 +16,6 @@ import com.meloda.fast.api.model.VkConversation
|
||||
import com.meloda.fast.api.model.VkGroup
|
||||
import com.meloda.fast.api.model.VkMessage
|
||||
import com.meloda.fast.api.model.VkUser
|
||||
import com.meloda.fast.api.model.attachments.VkAttachment
|
||||
import com.meloda.fast.api.model.attachments.VkPhoto
|
||||
import com.meloda.fast.base.adapter.BaseAdapter
|
||||
import com.meloda.fast.base.adapter.BaseHolder
|
||||
@@ -34,11 +32,7 @@ class MessagesHistoryAdapter constructor(
|
||||
val groups: HashMap<Int, VkGroup> = hashMapOf()
|
||||
) : BaseAdapter<VkMessage, MessagesHistoryAdapter.BasicHolder>(context, values, COMPARATOR) {
|
||||
|
||||
private var highlightTimer: Timer? = null
|
||||
|
||||
var onItemClickListener: ((position: Int, view: View) -> Unit)? = null
|
||||
|
||||
var attachmentClickListener: ((attachment: VkAttachment) -> Unit)? = null
|
||||
var avatarLongClickListener: ((position: Int) -> Unit)? = null
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
when {
|
||||
@@ -76,11 +70,19 @@ class MessagesHistoryAdapter constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun initListeners(itemView: View, position: Int) {
|
||||
if (itemView is AdapterView<*>) return
|
||||
// override fun initListeners(itemView: View, position: Int) {
|
||||
// if (itemView is AdapterView<*>) return
|
||||
//
|
||||
// itemView.setOnClickListener { onItemClickListener?.invoke(position, itemView) }
|
||||
// itemView.setOnLongClickListener { itemLongClickListener.invoke(position) }
|
||||
// }
|
||||
|
||||
itemView.setOnClickListener { onItemClickListener?.invoke(position, itemView) }
|
||||
itemView.setOnLongClickListener { itemLongClickListener.invoke(position) }
|
||||
|
||||
val actualSize get() = values.size
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
if (actualSize == 0) return 2
|
||||
return super.getItemCount() + 2
|
||||
}
|
||||
|
||||
private fun createEmptyView(size: Int) = View(context).apply {
|
||||
@@ -142,6 +144,11 @@ class MessagesHistoryAdapter constructor(
|
||||
).setPhotoClickListener {
|
||||
Toast.makeText(context, "Photo url: $it", Toast.LENGTH_LONG).show()
|
||||
}.prepare()
|
||||
|
||||
binding.avatar.setOnLongClickListener() {
|
||||
avatarLongClickListener?.invoke(position)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,11 +237,30 @@ class MessagesHistoryAdapter constructor(
|
||||
}
|
||||
}
|
||||
|
||||
val actualSize get() = values.size
|
||||
fun removeMessageById(id: Int): Int? {
|
||||
for (i in values.indices) {
|
||||
val message = values[i]
|
||||
if (message.id == id) {
|
||||
values.removeAt(i)
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
if (actualSize == 0) return 2
|
||||
return super.getItemCount() + 2
|
||||
return null
|
||||
}
|
||||
|
||||
fun removeMessagesByIds(ids: List<Int>): List<Int> {
|
||||
val positions = mutableListOf<Int>()
|
||||
|
||||
for (i in values.indices) {
|
||||
val message = values[i]
|
||||
if (ids.contains(message.id)) {
|
||||
values.removeAt(i)
|
||||
positions += i
|
||||
}
|
||||
}
|
||||
|
||||
return positions
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.viewbinding.library.fragment.viewBinding
|
||||
import android.widget.Toast
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
@@ -20,6 +21,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.meloda.fast.R
|
||||
import com.meloda.fast.api.UserConfig
|
||||
import com.meloda.fast.api.VKConstants
|
||||
import com.meloda.fast.api.VkUtils
|
||||
import com.meloda.fast.api.model.VkConversation
|
||||
import com.meloda.fast.api.model.VkGroup
|
||||
import com.meloda.fast.api.model.VkMessage
|
||||
@@ -28,6 +30,7 @@ import com.meloda.fast.base.BaseViewModelFragment
|
||||
import com.meloda.fast.base.viewmodel.StartProgressEvent
|
||||
import com.meloda.fast.base.viewmodel.StopProgressEvent
|
||||
import com.meloda.fast.base.viewmodel.VkEvent
|
||||
import com.meloda.fast.databinding.DialogMessageDeleteBinding
|
||||
import com.meloda.fast.databinding.FragmentMessagesHistoryBinding
|
||||
import com.meloda.fast.extensions.TextViewExtensions.clear
|
||||
import com.meloda.fast.util.AndroidUtils
|
||||
@@ -65,8 +68,9 @@ class MessagesHistoryFragment :
|
||||
|
||||
private val adapter: MessagesHistoryAdapter by lazy {
|
||||
MessagesHistoryAdapter(requireContext(), mutableListOf(), conversation).also {
|
||||
it.onItemClickListener = this::onItemClick
|
||||
it.itemClickListener = this::onItemClick
|
||||
it.itemLongClickListener = this::onItemLongClick
|
||||
it.avatarLongClickListener = this::onAvatarLongClickListener
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,11 +320,14 @@ class MessagesHistoryFragment :
|
||||
super.onEvent(event)
|
||||
|
||||
when (event) {
|
||||
is StartProgressEvent -> onProgressStarted()
|
||||
is StopProgressEvent -> onProgressStopped()
|
||||
|
||||
is MessagesMarkAsImportant -> markMessagesAsImportant(event)
|
||||
is MessagesLoaded -> refreshMessages(event)
|
||||
is MessagesPin -> conversation.pinnedMessage = event.message
|
||||
is StartProgressEvent -> onProgressStarted()
|
||||
is StopProgressEvent -> onProgressStopped()
|
||||
is MessagesUnpin -> conversation.pinnedMessage = null
|
||||
is MessagesDelete -> deleteMessages(event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,7 +409,23 @@ class MessagesHistoryFragment :
|
||||
else binding.recyclerView.scrollToPosition(adapter.lastPosition)
|
||||
}
|
||||
|
||||
private fun onItemClick(position: Int, view: View) {
|
||||
private fun onItemClick(position: Int) {
|
||||
showOptionsDialog(position)
|
||||
}
|
||||
|
||||
private fun onItemLongClick(position: Int) = true
|
||||
|
||||
private fun onAvatarLongClickListener(position: Int) {
|
||||
val message = adapter.values[position]
|
||||
|
||||
val messageUser = VkUtils.getMessageUser(message, adapter.profiles)
|
||||
val messageGroup = VkUtils.getMessageGroup(message, adapter.groups)
|
||||
|
||||
val title = VkUtils.getMessageTitle(message, messageUser, messageGroup)
|
||||
Toast.makeText(requireContext(), title, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun showOptionsDialog(position: Int) {
|
||||
val message = adapter.values[position]
|
||||
if (message.action != null) return
|
||||
|
||||
@@ -414,6 +437,11 @@ class MessagesHistoryFragment :
|
||||
).format(message.date * 1000L)
|
||||
)
|
||||
|
||||
val important = getString(
|
||||
if (message.important) R.string.message_context_action_unmark_as_important
|
||||
else R.string.message_context_action_mark_as_important
|
||||
)
|
||||
|
||||
val reply = getString(R.string.message_context_action_reply)
|
||||
|
||||
val isMessageAlreadyPinned = message.id == conversation.pinnedMessage?.id
|
||||
@@ -425,27 +453,25 @@ class MessagesHistoryFragment :
|
||||
|
||||
val edit = getString(R.string.message_context_action_edit)
|
||||
|
||||
val important = getString(
|
||||
if (message.important) R.string.message_context_action_unmark_as_important
|
||||
else R.string.message_context_action_mark_as_important
|
||||
val delete = getString(R.string.message_context_action_delete)
|
||||
|
||||
val params = mutableListOf(
|
||||
important, reply
|
||||
)
|
||||
|
||||
val params = mutableListOf<String>()
|
||||
params.add(reply)
|
||||
|
||||
if (conversation.canChangePin) {
|
||||
params.add(pin)
|
||||
params += pin
|
||||
}
|
||||
|
||||
if (message.canEdit()) {
|
||||
params.add(edit)
|
||||
params += edit
|
||||
}
|
||||
|
||||
params.add(important)
|
||||
params += delete
|
||||
|
||||
val arrayParams = params.toTypedArray()
|
||||
|
||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(time)
|
||||
.setItems(arrayParams) { _, which ->
|
||||
when (params[which]) {
|
||||
@@ -468,16 +494,40 @@ class MessagesHistoryFragment :
|
||||
if (attachmentController.message.value != message)
|
||||
attachmentController.message.value = message
|
||||
}
|
||||
delete -> showDeleteMessageDialog(message)
|
||||
}
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
|
||||
}.show()
|
||||
}
|
||||
|
||||
private fun onItemLongClick(position: Int): Boolean {
|
||||
private fun showDeleteMessageDialog(message: VkMessage) {
|
||||
val binding = DialogMessageDeleteBinding.inflate(layoutInflater, null, false)
|
||||
|
||||
return true
|
||||
binding.check.setText(
|
||||
if (message.isOut) R.string.message_delete_for_all
|
||||
else R.string.message_mark_as_spam
|
||||
)
|
||||
|
||||
binding.check.isEnabled = !message.isOut || message.canEdit()
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.confirm_delete_message)
|
||||
.setView(binding.root)
|
||||
.setPositiveButton(R.string.action_delete) { _, _ ->
|
||||
viewModel.deleteMessage(
|
||||
peerId = conversation.id,
|
||||
messagesIds = listOf(message.id),
|
||||
isSpam = if (message.isOut) null else binding.check.isChecked,
|
||||
deleteForAll = if (!message.isOut || !message.canEdit()) null else binding.check.isChecked
|
||||
)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun deleteMessages(event: MessagesDelete) {
|
||||
adapter.removeMessagesByIds(event.messagesIds).let {
|
||||
it.forEach { index -> adapter.notifyItemRemoved(index) }
|
||||
}
|
||||
}
|
||||
|
||||
private inner class AttachmentPanelController {
|
||||
|
||||
@@ -154,6 +154,26 @@ class MessagesHistoryViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteMessage(
|
||||
peerId: Int,
|
||||
messagesIds: List<Int>? = null,
|
||||
conversationsMessagesIds: List<Int>? = null,
|
||||
isSpam: Boolean? = null,
|
||||
deleteForAll: Boolean? = null
|
||||
) = viewModelScope.launch {
|
||||
makeJob({
|
||||
messages.delete(
|
||||
MessagesDeleteRequest(
|
||||
peerId = peerId,
|
||||
messagesIds = messagesIds,
|
||||
conversationsMessagesIds = conversationsMessagesIds,
|
||||
isSpam = isSpam,
|
||||
deleteForAll = deleteForAll
|
||||
)
|
||||
)
|
||||
}, onAnswer = { sendEvent(MessagesDelete(messagesIds = messagesIds ?: listOf())) })
|
||||
}
|
||||
}
|
||||
|
||||
data class MessagesLoaded(
|
||||
@@ -175,3 +195,7 @@ data class MessagesPin(
|
||||
|
||||
object MessagesUnpin : VkEvent()
|
||||
|
||||
data class MessagesDelete(
|
||||
val messagesIds: List<Int>
|
||||
) : VkEvent()
|
||||
|
||||
|
||||
@@ -76,13 +76,8 @@ class MessagesPreparator constructor(
|
||||
}
|
||||
|
||||
fun prepare() {
|
||||
val messageUser: VkUser? = (if (message.isUser()) {
|
||||
profiles[message.fromId]
|
||||
} else null).also { message.user.value = it }
|
||||
|
||||
val messageGroup: VkGroup? = (if (message.isGroup()) {
|
||||
groups[message.fromId]
|
||||
} else null).also { message.group.value = it }
|
||||
val messageUser = VkUtils.getMessageUser(message, profiles)
|
||||
val messageGroup = VkUtils.getMessageGroup(message, groups)
|
||||
|
||||
prepareRootBackground()
|
||||
|
||||
@@ -225,11 +220,7 @@ class MessagesPreparator constructor(
|
||||
messageGroup: VkGroup? = null
|
||||
) {
|
||||
if (avatar != null) {
|
||||
val avatarUrl = when {
|
||||
message.isUser() && messageUser != null && !messageUser.photo200.isNullOrBlank() -> messageUser.photo200
|
||||
message.isGroup() && messageGroup != null && !messageGroup.photo200.isNullOrBlank() -> messageGroup.photo200
|
||||
else -> null
|
||||
}
|
||||
val avatarUrl = VkUtils.getMessageAvatar(message, messageUser, messageGroup)
|
||||
|
||||
avatar.load(avatarUrl) { crossfade(100) }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="12dp">
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/check"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:text="@string/message_delete_for_all"
|
||||
app:useMaterialThemeColors="true" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</layout>
|
||||
@@ -121,4 +121,15 @@
|
||||
<string name="message_context_action_pin">Pin</string>
|
||||
<string name="message_context_action_unpin">Unpin</string>
|
||||
<string name="message_context_action_edit">Edit</string>
|
||||
<string name="message_context_action_delete">Delete</string>
|
||||
|
||||
<string name="confirm_delete_message">Delete the message?</string>
|
||||
|
||||
<string name="message_delete_for_all">Delete for all</string>
|
||||
|
||||
<string name="message_mark_as_spam">Mark as spam</string>
|
||||
|
||||
<string name="action_delete">Delete</string>
|
||||
<string name="conversation_context_action_delete">Delete</string>
|
||||
<string name="confirm_delete_conversation">Delete the conversation?</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user