messages pin & unpin feature
fix avatars and titles visual improvements other bugfixes & minor changes
This commit is contained in:
@@ -10,27 +10,28 @@ import kotlinx.parcelize.Parcelize
|
||||
@Parcelize
|
||||
data class VkConversation(
|
||||
@PrimaryKey(autoGenerate = false)
|
||||
val id: Int,
|
||||
val ownerId: Int?,
|
||||
val title: String?,
|
||||
val photo200: String?,
|
||||
val type: String,
|
||||
val callInProgress: Boolean,
|
||||
val isPhantom: Boolean,
|
||||
val lastConversationMessageId: Int,
|
||||
val inRead: Int,
|
||||
val outRead: Int,
|
||||
val isMarkedUnread: Boolean,
|
||||
val lastMessageId: Int,
|
||||
val unreadCount: Int?,
|
||||
val membersCount: Int?,
|
||||
val isPinned: Boolean,
|
||||
var id: Int,
|
||||
var ownerId: Int?,
|
||||
var title: String?,
|
||||
var photo200: String?,
|
||||
var type: String,
|
||||
var callInProgress: Boolean,
|
||||
var isPhantom: Boolean,
|
||||
var lastConversationMessageId: Int,
|
||||
var inRead: Int,
|
||||
var outRead: Int,
|
||||
var isMarkedUnread: Boolean,
|
||||
var lastMessageId: Int,
|
||||
var unreadCount: Int?,
|
||||
var membersCount: Int?,
|
||||
var isPinned: Boolean,
|
||||
var canChangePin: Boolean,
|
||||
|
||||
@Embedded(prefix = "pinnedMessage_")
|
||||
var pinnedMessage: VkMessage? = null,
|
||||
|
||||
@Embedded(prefix = "lastMessage_")
|
||||
var lastMessage: VkMessage? = null
|
||||
var lastMessage: VkMessage? = null,
|
||||
) : Parcelable {
|
||||
|
||||
fun isChat() = type == "chat"
|
||||
|
||||
@@ -4,8 +4,10 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.PrimaryKey
|
||||
import com.meloda.fast.api.UserConfig
|
||||
import com.meloda.fast.api.model.attachments.VkAttachment
|
||||
import com.meloda.fast.base.adapter.SelectableItem
|
||||
import com.meloda.fast.util.TimeUtils
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@@ -58,6 +60,10 @@ data class VkMessage(
|
||||
return Action.parse(action)
|
||||
}
|
||||
|
||||
fun canEdit() =
|
||||
fromId == UserConfig.userId &&
|
||||
(System.currentTimeMillis() / 1000 - date.toLong() < TimeUtils.ONE_DAY_IN_SECONDS)
|
||||
|
||||
fun copyMessage(
|
||||
id: Int = this.id,
|
||||
text: String? = this.text,
|
||||
|
||||
@@ -40,7 +40,8 @@ data class BaseVkConversation(
|
||||
unreadCount = unread_count,
|
||||
membersCount = chat_settings?.members_count,
|
||||
ownerId = chat_settings?.owner_id,
|
||||
isPinned = sort_id.major_id > 0
|
||||
isPinned = sort_id.major_id > 0,
|
||||
canChangePin = chat_settings?.acl?.can_change_pin == true
|
||||
).apply {
|
||||
this.lastMessage = lastMessage
|
||||
this.pinnedMessage = chat_settings?.pinned_message?.asVkMessage()
|
||||
|
||||
@@ -70,6 +70,28 @@ data class MessagesMarkAsImportantRequest(
|
||||
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class MessagesPinMessageRequest(
|
||||
val peerId: Int,
|
||||
val messageId: Int? = null,
|
||||
val conversationMessageId: Int? = null
|
||||
) : Parcelable {
|
||||
|
||||
val map
|
||||
get() = mutableMapOf(
|
||||
"peer_id" to peerId.toString()
|
||||
).apply {
|
||||
messageId?.let { this["message_id"] = it.toString() }
|
||||
conversationMessageId?.let { this["conversation_message_id"] = it.toString() }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class MessagesUnPinMessageRequest(val peerId: Int) : Parcelable {
|
||||
val map get() = mutableMapOf("peer_id" to peerId.toString())
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class MessagesGetLongPollServerRequest(
|
||||
val needPts: Boolean,
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
package com.meloda.fast.api.network
|
||||
|
||||
object VKUrls {
|
||||
object VkUrls {
|
||||
|
||||
const val OAUTH = "https://oauth.vk.com"
|
||||
const val API = "https://api.vk.com/method"
|
||||
@@ -22,6 +22,8 @@ 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"
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package com.meloda.fast.api.network.datasource
|
||||
|
||||
import com.meloda.fast.api.model.VkMessage
|
||||
import com.meloda.fast.api.model.request.MessagesGetHistoryRequest
|
||||
import com.meloda.fast.api.model.request.MessagesGetLongPollServerRequest
|
||||
import com.meloda.fast.api.model.request.MessagesMarkAsImportantRequest
|
||||
import com.meloda.fast.api.model.request.MessagesSendRequest
|
||||
import com.meloda.fast.api.model.request.*
|
||||
import com.meloda.fast.api.network.repo.MessagesRepo
|
||||
import com.meloda.fast.database.dao.MessagesDao
|
||||
import javax.inject.Inject
|
||||
@@ -26,8 +23,14 @@ class MessagesDataSource @Inject constructor(
|
||||
suspend fun getLongPollServer(params: MessagesGetLongPollServerRequest) =
|
||||
repo.getLongPollServer(params.map)
|
||||
|
||||
suspend fun storeMessages(messages: List<VkMessage>) = dao.insert(messages)
|
||||
suspend fun pin(params: MessagesPinMessageRequest) =
|
||||
repo.pin(params.map)
|
||||
|
||||
suspend fun getCachedMessages(peerId: Int) = dao.getByPeerId(peerId)
|
||||
suspend fun unpin(params: MessagesUnPinMessageRequest) =
|
||||
repo.unpin(params.map)
|
||||
|
||||
suspend fun store(messages: List<VkMessage>) = dao.insert(messages)
|
||||
|
||||
suspend fun getCached(peerId: Int) = dao.getByPeerId(peerId)
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.meloda.fast.api.network.repo
|
||||
|
||||
import com.meloda.fast.api.network.VKUrls
|
||||
import com.meloda.fast.api.network.VkUrls
|
||||
import com.meloda.fast.api.model.response.ResponseAuthDirect
|
||||
import com.meloda.fast.api.network.Answer
|
||||
import com.meloda.fast.api.model.response.ResponseSendSms
|
||||
@@ -8,10 +8,10 @@ import retrofit2.http.*
|
||||
|
||||
interface AuthRepo {
|
||||
|
||||
@GET(VKUrls.Auth.DirectAuth)
|
||||
@GET(VkUrls.Auth.DirectAuth)
|
||||
suspend fun auth(@QueryMap param: Map<String, String?>): Answer<ResponseAuthDirect>
|
||||
|
||||
@GET(VKUrls.Auth.SendSms)
|
||||
@GET(VkUrls.Auth.SendSms)
|
||||
suspend fun sendSms(@Query("sid") validationSid: String): Answer<ResponseSendSms>
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package com.meloda.fast.api.network.repo
|
||||
|
||||
import com.meloda.fast.api.base.ApiResponse
|
||||
import com.meloda.fast.api.network.Answer
|
||||
import com.meloda.fast.api.network.VKUrls
|
||||
import com.meloda.fast.api.network.VkUrls
|
||||
import com.meloda.fast.api.model.response.ConversationsGetResponse
|
||||
import retrofit2.http.FieldMap
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
@@ -11,7 +11,7 @@ import retrofit2.http.POST
|
||||
interface ConversationsRepo {
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST(VKUrls.Conversations.Get)
|
||||
@POST(VkUrls.Conversations.Get)
|
||||
suspend fun getAllChats(@FieldMap params: Map<String, String>): Answer<ApiResponse<ConversationsGetResponse>>
|
||||
|
||||
}
|
||||
@@ -2,9 +2,10 @@ package com.meloda.fast.api.network.repo
|
||||
|
||||
import com.meloda.fast.api.base.ApiResponse
|
||||
import com.meloda.fast.api.model.base.BaseVkLongPoll
|
||||
import com.meloda.fast.api.model.base.BaseVkMessage
|
||||
import com.meloda.fast.api.model.response.MessagesGetHistoryResponse
|
||||
import com.meloda.fast.api.network.Answer
|
||||
import com.meloda.fast.api.network.VKUrls
|
||||
import com.meloda.fast.api.network.VkUrls
|
||||
import retrofit2.http.FieldMap
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.POST
|
||||
@@ -12,19 +13,27 @@ import retrofit2.http.POST
|
||||
interface MessagesRepo {
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST(VKUrls.Messages.GetHistory)
|
||||
@POST(VkUrls.Messages.GetHistory)
|
||||
suspend fun getHistory(@FieldMap params: Map<String, String>): Answer<ApiResponse<MessagesGetHistoryResponse>>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST(VKUrls.Messages.Send)
|
||||
@POST(VkUrls.Messages.Send)
|
||||
suspend fun send(@FieldMap params: Map<String, String>): Answer<ApiResponse<Int>>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST(VKUrls.Messages.MarkAsImportant)
|
||||
@POST(VkUrls.Messages.MarkAsImportant)
|
||||
suspend fun markAsImportant(@FieldMap params: Map<String, String>): Answer<ApiResponse<List<Int>>>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST(VKUrls.Messages.GetLongPollServer)
|
||||
@POST(VkUrls.Messages.GetLongPollServer)
|
||||
suspend fun getLongPollServer(@FieldMap params: Map<String, String>): Answer<ApiResponse<BaseVkLongPoll>>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST(VkUrls.Messages.Pin)
|
||||
suspend fun pin(@FieldMap params: Map<String, String>): Answer<ApiResponse<BaseVkMessage>>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST(VkUrls.Messages.Unpin)
|
||||
suspend fun unpin(@FieldMap params: Map<String, String>): Answer<ApiResponse<Any>>
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package com.meloda.fast.api.network.repo
|
||||
import com.meloda.fast.api.base.ApiResponse
|
||||
import com.meloda.fast.api.model.base.BaseVkUser
|
||||
import com.meloda.fast.api.network.Answer
|
||||
import com.meloda.fast.api.network.VKUrls
|
||||
import com.meloda.fast.api.network.VkUrls
|
||||
import retrofit2.http.FieldMap
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.POST
|
||||
@@ -11,7 +11,7 @@ import retrofit2.http.POST
|
||||
interface UsersRepo {
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST(VKUrls.Users.GetById)
|
||||
@POST(VkUrls.Users.GetById)
|
||||
suspend fun getById(
|
||||
@FieldMap params: Map<String, String>?
|
||||
): Answer<ApiResponse<List<BaseVkUser>>>
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.meloda.fast.activity.MainActivity
|
||||
import com.meloda.fast.api.UserConfig
|
||||
import com.meloda.fast.base.viewmodel.BaseViewModel
|
||||
import com.meloda.fast.base.viewmodel.IllegalTokenEvent
|
||||
import com.meloda.fast.base.viewmodel.VKEvent
|
||||
import com.meloda.fast.base.viewmodel.VkEvent
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
@@ -30,7 +30,7 @@ abstract class BaseViewModelFragment<VM : BaseViewModel> : BaseFragment {
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onEvent(event: VKEvent) {
|
||||
protected open fun onEvent(event: VkEvent) {
|
||||
if (event is IllegalTokenEvent) {
|
||||
Toast.makeText(
|
||||
requireContext(), R.string.authorization_failed, Toast.LENGTH_LONG
|
||||
|
||||
@@ -15,7 +15,7 @@ abstract class BaseViewModel : ViewModel() {
|
||||
|
||||
var unknownErrorDefaultText: String = ""
|
||||
|
||||
protected val tasksEventChannel = Channel<VKEvent>()
|
||||
protected val tasksEventChannel = Channel<VkEvent>()
|
||||
val tasksEvent = tasksEventChannel.receiveAsFlow()
|
||||
|
||||
protected fun <T> makeJob(
|
||||
@@ -25,22 +25,35 @@ abstract class BaseViewModel : ViewModel() {
|
||||
onEnd: (suspend () -> Unit)? = null,
|
||||
onError: (suspend (Throwable) -> Unit)? = null
|
||||
) = viewModelScope.launch {
|
||||
onStart?.invoke()
|
||||
onStart?.invoke() ?: onStart()
|
||||
when (val response = job()) {
|
||||
is Answer.Success -> onAnswer(response.data)
|
||||
is Answer.Error -> {
|
||||
checkErrors(response.throwable)
|
||||
onError?.invoke(response.throwable) ?: sendEvent(
|
||||
ErrorEvent(
|
||||
response.throwable.message
|
||||
?: unknownErrorDefaultText
|
||||
)
|
||||
)
|
||||
onError?.invoke(response.throwable) ?: onError(response.throwable)
|
||||
}
|
||||
}
|
||||
}.also { it.invokeOnCompletion { viewModelScope.launch { onEnd?.invoke() } } }
|
||||
}.also {
|
||||
it.invokeOnCompletion {
|
||||
viewModelScope.launch {
|
||||
onEnd?.invoke() ?: onStop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected suspend fun <T : VKEvent> sendEvent(event: T) = tasksEventChannel.send(event)
|
||||
protected suspend fun onStart() {
|
||||
sendEvent(StartProgressEvent)
|
||||
}
|
||||
|
||||
protected suspend fun onStop() {
|
||||
sendEvent(StopProgressEvent)
|
||||
}
|
||||
|
||||
protected suspend fun onError(throwable: Throwable) {
|
||||
sendEvent(ErrorEvent(throwable.message ?: unknownErrorDefaultText))
|
||||
}
|
||||
|
||||
protected suspend fun <T : VkEvent> sendEvent(event: T) = tasksEventChannel.send(event)
|
||||
|
||||
private suspend fun checkErrors(throwable: Throwable) {
|
||||
when (throwable) {
|
||||
|
||||
@@ -5,15 +5,15 @@ data class ShowDialogInfoEvent(
|
||||
val message: String,
|
||||
val positiveBtn: String? = null,
|
||||
val negativeBtn: String? = null
|
||||
) : VKEvent()
|
||||
) : VkEvent()
|
||||
|
||||
data class ErrorEvent(val errorText: String) : VKEvent()
|
||||
data class ErrorEvent(val errorText: String) : VkEvent()
|
||||
|
||||
object IllegalTokenEvent : VKEvent()
|
||||
data class CaptchaEvent(val sid: String, val image: String) : VKEvent()
|
||||
data class ValidationEvent(val sid: String) : VKEvent()
|
||||
object IllegalTokenEvent : VkEvent()
|
||||
data class CaptchaEvent(val sid: String, val image: String) : VkEvent()
|
||||
data class ValidationEvent(val sid: String) : VkEvent()
|
||||
|
||||
object StartProgressEvent : VKEvent()
|
||||
object StopProgressEvent : VKEvent()
|
||||
object StartProgressEvent : VkEvent()
|
||||
object StopProgressEvent : VkEvent()
|
||||
|
||||
abstract class VKEvent
|
||||
abstract class VkEvent
|
||||
@@ -19,7 +19,7 @@ import com.meloda.fast.database.dao.UsersDao
|
||||
VkUser::class,
|
||||
VkGroup::class
|
||||
],
|
||||
version = 25,
|
||||
version = 26,
|
||||
exportSchema = false,
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
|
||||
@@ -24,7 +24,7 @@ import com.meloda.fast.api.model.VkConversation
|
||||
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.base.viewmodel.VkEvent
|
||||
import com.meloda.fast.common.AppGlobal
|
||||
import com.meloda.fast.common.AppSettings
|
||||
import com.meloda.fast.common.dataStore
|
||||
@@ -185,7 +185,7 @@ class ConversationsFragment :
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onEvent(event: VKEvent) {
|
||||
override fun onEvent(event: VkEvent) {
|
||||
super.onEvent(event)
|
||||
when (event) {
|
||||
is ConversationsLoaded -> refreshConversations(event)
|
||||
|
||||
+4
-11
@@ -11,9 +11,7 @@ import com.meloda.fast.api.model.request.UsersGetRequest
|
||||
import com.meloda.fast.api.network.datasource.ConversationsDataSource
|
||||
import com.meloda.fast.api.network.datasource.UsersDataSource
|
||||
import com.meloda.fast.base.viewmodel.BaseViewModel
|
||||
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.base.viewmodel.VkEvent
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -66,13 +64,8 @@ class ConversationsViewModel @Inject constructor(
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
onError = {
|
||||
val er = it
|
||||
throw it
|
||||
},
|
||||
onStart = { sendEvent(StartProgressEvent) },
|
||||
onEnd = { sendEvent(StopProgressEvent) })
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun loadProfileUser() = viewModelScope.launch {
|
||||
@@ -95,4 +88,4 @@ data class ConversationsLoaded(
|
||||
val conversations: List<VkConversation>,
|
||||
val profiles: HashMap<Int, VkUser>,
|
||||
val groups: HashMap<Int, VkGroup>
|
||||
) : VKEvent()
|
||||
) : VkEvent()
|
||||
|
||||
@@ -70,7 +70,7 @@ class LoginFragment : BaseViewModelFragment<LoginViewModel>(R.layout.fragment_lo
|
||||
binding.loginInput.clearFocus()
|
||||
}
|
||||
|
||||
override fun onEvent(event: VKEvent) {
|
||||
override fun onEvent(event: VkEvent) {
|
||||
super.onEvent(event)
|
||||
|
||||
when (event) {
|
||||
|
||||
@@ -51,26 +51,25 @@ class LoginViewModel @Inject constructor(
|
||||
sendEvent(SuccessAuth())
|
||||
},
|
||||
onError = {
|
||||
if (it !is VKException) return@makeJob
|
||||
if (it !is VKException) {
|
||||
onError(it)
|
||||
return@makeJob
|
||||
}
|
||||
|
||||
// TODO: 9/27/2021 use `delay` parameter
|
||||
twoFaCode?.let { sendEvent(CodeSent) }
|
||||
},
|
||||
onStart = { sendEvent(StartProgressEvent) },
|
||||
onEnd = { sendEvent(StopProgressEvent) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun sendSms(validationSid: String) = viewModelScope.launch {
|
||||
makeJob({ dataSource.sendSms(validationSid) },
|
||||
onAnswer = { sendEvent(CodeSent) },
|
||||
onError = {},
|
||||
onStart = {},
|
||||
onEnd = {})
|
||||
onAnswer = { sendEvent(CodeSent) }
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object CodeSent : VKEvent()
|
||||
object CodeSent : VkEvent()
|
||||
|
||||
data class SuccessAuth(val haveAuthorized: Boolean = true) : VKEvent()
|
||||
data class SuccessAuth(val haveAuthorized: Boolean = true) : VkEvent()
|
||||
@@ -45,6 +45,13 @@ class AttachmentInflater constructor(
|
||||
private val playColor = ContextCompat.getColor(context, R.color.a3_700)
|
||||
private val playBackgroundColor = ContextCompat.getColor(context, R.color.a3_200)
|
||||
|
||||
var photoClickListener: ((url: String) -> Unit)? = null
|
||||
|
||||
fun setPhotoClickListener(unit: ((url: String) -> Unit)?): AttachmentInflater {
|
||||
this.photoClickListener = unit
|
||||
return this
|
||||
}
|
||||
|
||||
fun inflate() {
|
||||
if (message.attachments.isNullOrEmpty()) return
|
||||
attachments = message.attachments!!
|
||||
@@ -114,6 +121,12 @@ class AttachmentInflater constructor(
|
||||
scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
}
|
||||
|
||||
if (photoClickListener != null) {
|
||||
newPhoto.setOnClickListener { photoClickListener?.invoke(size.url) }
|
||||
} else {
|
||||
newPhoto.setOnClickListener(null)
|
||||
}
|
||||
|
||||
val spacer = Space(context).also {
|
||||
it.layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
|
||||
@@ -6,6 +6,7 @@ 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
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
@@ -16,6 +17,7 @@ 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
|
||||
@@ -36,6 +38,8 @@ class MessagesHistoryAdapter constructor(
|
||||
|
||||
var onItemClickListener: ((position: Int, view: View) -> Unit)? = null
|
||||
|
||||
var attachmentClickListener: ((attachment: VkAttachment) -> Unit)? = null
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
when {
|
||||
isPositionHeader(position) -> return HEADER
|
||||
@@ -123,6 +127,8 @@ class MessagesHistoryAdapter constructor(
|
||||
prevMessage = prevMessage,
|
||||
nextMessage = nextMessage,
|
||||
|
||||
title = binding.title,
|
||||
|
||||
avatar = binding.avatar,
|
||||
bubble = binding.bubble,
|
||||
text = binding.text,
|
||||
@@ -133,7 +139,9 @@ class MessagesHistoryAdapter constructor(
|
||||
|
||||
profiles = profiles,
|
||||
groups = groups
|
||||
).prepare()
|
||||
).setPhotoClickListener {
|
||||
Toast.makeText(context, "Photo url: $it", Toast.LENGTH_LONG).show()
|
||||
}.prepare()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,10 +27,9 @@ import com.meloda.fast.api.model.VkUser
|
||||
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.base.viewmodel.VkEvent
|
||||
import com.meloda.fast.databinding.FragmentMessagesHistoryBinding
|
||||
import com.meloda.fast.extensions.TextViewExtensions.clear
|
||||
import com.meloda.fast.extensions.isNotVisible
|
||||
import com.meloda.fast.util.AndroidUtils
|
||||
import com.meloda.fast.util.TimeUtils
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -49,7 +48,7 @@ class MessagesHistoryFragment :
|
||||
private val action = MutableLiveData<Action>()
|
||||
|
||||
private enum class Action {
|
||||
RECORD, SEND
|
||||
RECORD, SEND, EDIT
|
||||
}
|
||||
|
||||
private val user: VkUser? by lazy {
|
||||
@@ -71,14 +70,15 @@ class MessagesHistoryFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private val replyMessage = MutableLiveData<VkMessage?>()
|
||||
private val isAttachmentPanelVisible = MutableLiveData(false)
|
||||
|
||||
private var timestampTimer: Timer? = null
|
||||
|
||||
private lateinit var attachmentController: AttachmentPanelController
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
attachmentController = AttachmentPanelController().init()
|
||||
|
||||
val title = when {
|
||||
conversation.isChat() -> conversation.title
|
||||
conversation.isUser() -> user?.toString()
|
||||
@@ -167,8 +167,11 @@ class MessagesHistoryFragment :
|
||||
it.toString().isNotBlank()
|
||||
|
||||
val newValue =
|
||||
if (canSend) Action.SEND
|
||||
else Action.RECORD
|
||||
when {
|
||||
attachmentController.isEditing -> Action.EDIT
|
||||
canSend -> Action.SEND
|
||||
else -> Action.RECORD
|
||||
}
|
||||
|
||||
if (action.value != newValue) action.value = newValue
|
||||
}
|
||||
@@ -193,30 +196,21 @@ class MessagesHistoryFragment :
|
||||
Action.SEND -> {
|
||||
binding.action.setImageResource(R.drawable.ic_round_send_24)
|
||||
}
|
||||
Action.EDIT -> {
|
||||
binding.action.setImageResource(R.drawable.ic_round_done_24)
|
||||
}
|
||||
else -> return@observe
|
||||
}
|
||||
}
|
||||
|
||||
isAttachmentPanelVisible.observe(viewLifecycleOwner) {
|
||||
attachmentController.isPanelVisible.observe(viewLifecycleOwner) {
|
||||
val layoutParams = binding.refreshLayout.layoutParams as CoordinatorLayout.LayoutParams
|
||||
layoutParams.bottomMargin =
|
||||
if (it) (binding.attachmentPanel.height / 1.5).roundToInt() else 0
|
||||
}
|
||||
|
||||
hideAttachmentPanel(duration = 1)
|
||||
|
||||
binding.avatar.setOnClickListener {
|
||||
val isShown = binding.attachmentPanel.isVisible
|
||||
|
||||
if (isShown) {
|
||||
hideAttachmentPanel()
|
||||
} else {
|
||||
showAttachmentPanel()
|
||||
}
|
||||
}
|
||||
|
||||
binding.attachmentPanel.setOnClickListener c@{
|
||||
val message = replyMessage.value ?: return@c
|
||||
val message = attachmentController.message.value ?: return@c
|
||||
|
||||
val index = adapter.values.indexOf(message)
|
||||
if (index == -1) return@c
|
||||
@@ -225,9 +219,8 @@ class MessagesHistoryFragment :
|
||||
}
|
||||
|
||||
binding.dismissReply.setOnClickListener {
|
||||
if (replyMessage.value != null) replyMessage.value = null
|
||||
|
||||
hideAttachmentPanel()
|
||||
if (attachmentController.message.value != null)
|
||||
attachmentController.message.value = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,28 +275,6 @@ class MessagesHistoryFragment :
|
||||
binding.pin.isVisible = conversation.isPinned
|
||||
}
|
||||
|
||||
private fun showAttachmentPanel(duration: Long = 250) {
|
||||
if (isAttachmentPanelVisible.value == false) isAttachmentPanelVisible.value = true
|
||||
|
||||
binding.attachmentPanel.animate()
|
||||
.translationY(0f)
|
||||
.alpha(1f)
|
||||
.setDuration(duration)
|
||||
.withStartAction { binding.attachmentPanel.isVisible = true }
|
||||
.start()
|
||||
}
|
||||
|
||||
private fun hideAttachmentPanel(duration: Long = 250) {
|
||||
if (isAttachmentPanelVisible.value == true) isAttachmentPanelVisible.value = false
|
||||
|
||||
binding.attachmentPanel.animate()
|
||||
.alpha(0f)
|
||||
.translationY(50f)
|
||||
.setDuration(duration)
|
||||
.withEndAction { binding.attachmentPanel.isVisible = false }
|
||||
.start()
|
||||
}
|
||||
|
||||
private fun performAction() {
|
||||
if (action.value == Action.RECORD) {
|
||||
return
|
||||
@@ -321,7 +292,7 @@ class MessagesHistoryFragment :
|
||||
fromId = UserConfig.userId,
|
||||
date = (date / 1000).toInt(),
|
||||
randomId = 0,
|
||||
replyMessage = replyMessage.value
|
||||
replyMessage = attachmentController.message.value
|
||||
)
|
||||
|
||||
adapter.add(message)
|
||||
@@ -329,10 +300,8 @@ class MessagesHistoryFragment :
|
||||
binding.recyclerView.smoothScrollToPosition(adapter.lastPosition)
|
||||
binding.message.clear()
|
||||
|
||||
val replyMessage = replyMessage.value
|
||||
|
||||
this.replyMessage.value = null
|
||||
hideAttachmentPanel()
|
||||
val replyMessage = attachmentController.message.value
|
||||
attachmentController.message.value = null
|
||||
|
||||
viewModel.sendMessage(
|
||||
peerId = conversation.id,
|
||||
@@ -343,12 +312,13 @@ class MessagesHistoryFragment :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEvent(event: VKEvent) {
|
||||
override fun onEvent(event: VkEvent) {
|
||||
super.onEvent(event)
|
||||
|
||||
when (event) {
|
||||
is MessagesMarkAsImportant -> markMessagesAsImportant(event)
|
||||
is MessagesLoaded -> refreshMessages(event)
|
||||
is MessagesPin -> conversation.pinnedMessage = event.message
|
||||
is StartProgressEvent -> onProgressStarted()
|
||||
is StopProgressEvent -> onProgressStopped()
|
||||
}
|
||||
@@ -436,106 +406,67 @@ class MessagesHistoryFragment :
|
||||
val message = adapter.values[position]
|
||||
if (message.action != null) return
|
||||
|
||||
// val popupMenu = PopupMenu(requireContext(), view)
|
||||
//
|
||||
// val reply = popupMenu.menu.add(
|
||||
// getString(R.string.message_context_action_reply)
|
||||
// )
|
||||
//
|
||||
// reply.icon =
|
||||
// ContextCompat.getDrawable(
|
||||
// requireContext(),
|
||||
// R.drawable.ic_attachment_wall_reply
|
||||
// )?.constantState?.newDrawable()?.also {
|
||||
// it.setTint(
|
||||
// ContextCompat.getColor(
|
||||
// requireContext(),
|
||||
// R.color.textColorSecondaryVariant
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// val important = popupMenu.menu.add(
|
||||
// getString(
|
||||
// if (message.important) R.string.message_context_action_unmark_as_important
|
||||
// else R.string.message_context_action_mark_as_important
|
||||
// )
|
||||
// )
|
||||
//
|
||||
// important.icon =
|
||||
// ContextCompat.getDrawable(
|
||||
// requireContext(),
|
||||
// R.drawable.ic_star_border
|
||||
// )?.constantState?.newDrawable()?.also {
|
||||
// it.setTint(
|
||||
// ContextCompat.getColor(
|
||||
// requireContext(),
|
||||
// R.color.textColorSecondaryVariant
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// popupMenu.setForceShowIcon(true)
|
||||
// popupMenu.setOnMenuItemClickListener {
|
||||
// when (it) {
|
||||
// reply -> {
|
||||
// val title = when {
|
||||
// message.isGroup() && message.group.value != null -> message.group.value?.name
|
||||
// message.isUser() && message.user.value != null -> message.user.value?.fullName
|
||||
// else -> null
|
||||
// }
|
||||
//
|
||||
// if (replyMessage.value != message) replyMessage.value = message
|
||||
//
|
||||
// binding.replyMessageTitle.text = title
|
||||
// binding.replyMessageText.text = message.text ?: "[no_message]"
|
||||
//
|
||||
// if (binding.attachmentPanel.isNotVisible) binding.avatar.performClick()
|
||||
// true
|
||||
// }
|
||||
//
|
||||
// important -> {
|
||||
// viewModel.markAsImportant(
|
||||
// messagesIds = listOf(message.id),
|
||||
// important = !message.important
|
||||
// )
|
||||
// true
|
||||
// }
|
||||
//
|
||||
// else -> false
|
||||
// }
|
||||
// }
|
||||
// popupMenu.show()
|
||||
val time = getString(
|
||||
R.string.time_format,
|
||||
SimpleDateFormat(
|
||||
"dd.MM.yyyy, HH:mm:ss",
|
||||
Locale.getDefault()
|
||||
).format(message.date * 1000L)
|
||||
)
|
||||
|
||||
val reply = getString(R.string.message_context_action_reply)
|
||||
|
||||
val isMessageAlreadyPinned = message.id == conversation.pinnedMessage?.id
|
||||
|
||||
val pin = getString(
|
||||
if (isMessageAlreadyPinned) R.string.message_context_action_unpin
|
||||
else R.string.message_context_action_pin
|
||||
)
|
||||
|
||||
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 params = arrayOf(reply, important)
|
||||
val params = mutableListOf<String>()
|
||||
params.add(reply)
|
||||
|
||||
if (conversation.canChangePin) {
|
||||
params.add(pin)
|
||||
}
|
||||
|
||||
if (message.canEdit()) {
|
||||
params.add(edit)
|
||||
}
|
||||
|
||||
params.add(important)
|
||||
|
||||
val arrayParams = params.toTypedArray()
|
||||
|
||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setItems(params) { _, which ->
|
||||
.setTitle(time)
|
||||
.setItems(arrayParams) { _, which ->
|
||||
when (params[which]) {
|
||||
important -> viewModel.markAsImportant(
|
||||
messagesIds = listOf(message.id),
|
||||
important = !message.important
|
||||
)
|
||||
reply -> {
|
||||
val title = when {
|
||||
message.isGroup() && message.group.value != null -> message.group.value?.name
|
||||
message.isUser() && message.user.value != null -> message.user.value?.fullName
|
||||
else -> null
|
||||
}
|
||||
if (attachmentController.message.value != message)
|
||||
attachmentController.message.value = message
|
||||
}
|
||||
pin -> viewModel.pinMessage(
|
||||
peerId = conversation.id,
|
||||
messageId = message.id,
|
||||
pin = !isMessageAlreadyPinned
|
||||
)
|
||||
edit -> {
|
||||
attachmentController.isEditing = true
|
||||
|
||||
if (replyMessage.value != message) replyMessage.value = message
|
||||
|
||||
binding.replyMessageTitle.text = title
|
||||
binding.replyMessageText.text = message.text ?: "[no_message]"
|
||||
|
||||
if (binding.attachmentPanel.isNotVisible) binding.avatar.performClick()
|
||||
if (attachmentController.message.value != message)
|
||||
attachmentController.message.value = message
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -549,4 +480,78 @@ class MessagesHistoryFragment :
|
||||
return true
|
||||
}
|
||||
|
||||
private inner class AttachmentPanelController {
|
||||
val isPanelVisible = MutableLiveData(false)
|
||||
val message = MutableLiveData<VkMessage?>()
|
||||
|
||||
var isEditing = false
|
||||
|
||||
fun init(): AttachmentPanelController {
|
||||
message.observe(viewLifecycleOwner) { value ->
|
||||
if (value != null) {
|
||||
applyMessage(value)
|
||||
} else {
|
||||
clearMessage()
|
||||
}
|
||||
}
|
||||
|
||||
message.value = null
|
||||
return this
|
||||
}
|
||||
|
||||
private fun applyMessage(message: VkMessage) {
|
||||
showPanel()
|
||||
|
||||
val title = when {
|
||||
message.isGroup() && message.group.value != null -> message.group.value?.name
|
||||
message.isUser() && message.user.value != null -> message.user.value?.fullName
|
||||
else -> null
|
||||
}
|
||||
|
||||
binding.replyMessageTitle.text = title
|
||||
binding.replyMessageText.text = message.text ?: "[no_message]"
|
||||
|
||||
if (isEditing) {
|
||||
binding.message.setText(message.text ?: "[no_message]")
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearMessage() {
|
||||
hidePanel()
|
||||
|
||||
binding.replyMessageTitle.clear()
|
||||
binding.replyMessageText.clear()
|
||||
|
||||
if (isEditing) {
|
||||
isEditing = false
|
||||
binding.message.clear()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showPanel(duration: Long = 250) {
|
||||
if (attachmentController.isPanelVisible.value == false)
|
||||
attachmentController.isPanelVisible.value = true
|
||||
|
||||
binding.attachmentPanel.animate()
|
||||
.translationY(0f)
|
||||
.alpha(1f)
|
||||
.setDuration(duration)
|
||||
.withStartAction { binding.attachmentPanel.isVisible = true }
|
||||
.start()
|
||||
}
|
||||
|
||||
private fun hidePanel(duration: Long = 250) {
|
||||
if (attachmentController.isPanelVisible.value == true)
|
||||
attachmentController.isPanelVisible.value = false
|
||||
|
||||
binding.attachmentPanel.animate()
|
||||
.alpha(0f)
|
||||
.translationY(50f)
|
||||
.setDuration(duration)
|
||||
.withEndAction { binding.attachmentPanel.isVisible = false }
|
||||
.start()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,28 +6,24 @@ 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.request.MessagesGetHistoryRequest
|
||||
import com.meloda.fast.api.model.request.MessagesMarkAsImportantRequest
|
||||
import com.meloda.fast.api.model.request.MessagesSendRequest
|
||||
import com.meloda.fast.api.model.request.*
|
||||
import com.meloda.fast.api.network.datasource.MessagesDataSource
|
||||
import com.meloda.fast.base.viewmodel.BaseViewModel
|
||||
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.base.viewmodel.VkEvent
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class MessagesHistoryViewModel @Inject constructor(
|
||||
private val dataSource: MessagesDataSource
|
||||
private val messages: MessagesDataSource
|
||||
) : BaseViewModel() {
|
||||
|
||||
fun loadHistory(
|
||||
peerId: Int
|
||||
) = viewModelScope.launch {
|
||||
makeJob({
|
||||
dataSource.getHistory(
|
||||
messages.getHistory(
|
||||
MessagesGetHistoryRequest(
|
||||
count = 30,
|
||||
peerId = peerId,
|
||||
@@ -53,18 +49,18 @@ class MessagesHistoryViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
val messages = hashMapOf<Int, VkMessage>()
|
||||
val hashMessages = hashMapOf<Int, VkMessage>()
|
||||
response.items.forEach { baseMessage ->
|
||||
baseMessage.asVkMessage().let { message -> messages[message.id] = message }
|
||||
baseMessage.asVkMessage().let { message -> hashMessages[message.id] = message }
|
||||
}
|
||||
|
||||
dataSource.storeMessages(messages.values.toList())
|
||||
messages.store(hashMessages.values.toList())
|
||||
|
||||
val conversations = hashMapOf<Int, VkConversation>()
|
||||
response.conversations?.let { baseConversations ->
|
||||
baseConversations.forEach { baseConversation ->
|
||||
baseConversation.asVkConversation(
|
||||
messages[baseConversation.last_message_id]
|
||||
hashMessages[baseConversation.last_message_id]
|
||||
).let { conversation -> conversations[conversation.id] = conversation }
|
||||
}
|
||||
}
|
||||
@@ -75,16 +71,10 @@ class MessagesHistoryViewModel @Inject constructor(
|
||||
profiles = profiles,
|
||||
groups = groups,
|
||||
conversations = conversations,
|
||||
messages = messages.values.toList()
|
||||
messages = hashMessages.values.toList()
|
||||
)
|
||||
)
|
||||
},
|
||||
onError = {
|
||||
val throwable = it
|
||||
throw it
|
||||
},
|
||||
onStart = { sendEvent(StartProgressEvent) },
|
||||
onEnd = { sendEvent(StopProgressEvent) })
|
||||
})
|
||||
}
|
||||
|
||||
fun sendMessage(
|
||||
@@ -96,7 +86,7 @@ class MessagesHistoryViewModel @Inject constructor(
|
||||
) = viewModelScope.launch {
|
||||
makeJob(
|
||||
{
|
||||
dataSource.send(
|
||||
messages.send(
|
||||
MessagesSendRequest(
|
||||
peerId = peerId,
|
||||
randomId = randomId,
|
||||
@@ -108,10 +98,6 @@ class MessagesHistoryViewModel @Inject constructor(
|
||||
onAnswer = {
|
||||
val response = it.response ?: return@makeJob
|
||||
setId?.invoke(response)
|
||||
},
|
||||
onError = {
|
||||
val throwable = it
|
||||
val i = 0
|
||||
})
|
||||
}
|
||||
|
||||
@@ -120,7 +106,7 @@ class MessagesHistoryViewModel @Inject constructor(
|
||||
important: Boolean
|
||||
) = viewModelScope.launch {
|
||||
makeJob({
|
||||
dataSource.markAsImportant(
|
||||
messages.markAsImportant(
|
||||
MessagesMarkAsImportantRequest(
|
||||
messagesIds = messagesIds,
|
||||
important = important
|
||||
@@ -135,13 +121,39 @@ class MessagesHistoryViewModel @Inject constructor(
|
||||
important = important
|
||||
)
|
||||
)
|
||||
},
|
||||
onError = {
|
||||
val throwable = it
|
||||
val i = 0
|
||||
})
|
||||
}
|
||||
|
||||
fun pinMessage(
|
||||
peerId: Int,
|
||||
messageId: Int? = null,
|
||||
conversationMessageId: Int? = null,
|
||||
pin: Boolean
|
||||
) = viewModelScope.launch {
|
||||
if (pin) {
|
||||
makeJob({
|
||||
messages.pin(
|
||||
MessagesPinMessageRequest(
|
||||
peerId = peerId,
|
||||
messageId = messageId,
|
||||
conversationMessageId = conversationMessageId
|
||||
)
|
||||
)
|
||||
},
|
||||
onAnswer = {
|
||||
val response = it.response ?: return@makeJob
|
||||
sendEvent(MessagesPin(response.asVkMessage()))
|
||||
}
|
||||
)
|
||||
} else {
|
||||
makeJob({ messages.unpin(MessagesUnPinMessageRequest(peerId = peerId)) },
|
||||
onAnswer = {
|
||||
println("Fast::MessagesHistoryViewModel::unPin::Response::${it.response}")
|
||||
sendEvent(MessagesUnpin)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class MessagesLoaded(
|
||||
@@ -150,9 +162,16 @@ data class MessagesLoaded(
|
||||
val messages: List<VkMessage>,
|
||||
val profiles: HashMap<Int, VkUser>,
|
||||
val groups: HashMap<Int, VkGroup>
|
||||
) : VKEvent()
|
||||
) : VkEvent()
|
||||
|
||||
data class MessagesMarkAsImportant(
|
||||
val messagesIds: List<Int>,
|
||||
val important: Boolean
|
||||
) : VKEvent()
|
||||
) : VkEvent()
|
||||
|
||||
data class MessagesPin(
|
||||
val message: VkMessage
|
||||
) : VkEvent()
|
||||
|
||||
object MessagesUnpin : VkEvent()
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import android.widget.Space
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.LinearLayoutCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import coil.load
|
||||
import com.meloda.fast.R
|
||||
@@ -65,14 +64,17 @@ class MessagesPreparator constructor(
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background)
|
||||
private val backgroundMiddleOut =
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle)
|
||||
// private val backgroundStrokeOut =
|
||||
// ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_stroke)
|
||||
// private val backgroundMiddleStrokeOut =
|
||||
// ContextCompat.getDrawable(context, R.drawable.ic_message_out_background_middle_stroke)
|
||||
|
||||
private val rootHighlightedColor =
|
||||
ContextCompat.getColor(context, R.color.n2_100)
|
||||
|
||||
private var photoClickListener: ((url: String) -> Unit)? = null
|
||||
|
||||
fun setPhotoClickListener(unit: ((url: String) -> Unit)?): MessagesPreparator {
|
||||
this.photoClickListener = unit
|
||||
return this
|
||||
}
|
||||
|
||||
fun prepare() {
|
||||
val messageUser: VkUser? = (if (message.isUser()) {
|
||||
profiles[message.fromId]
|
||||
@@ -104,20 +106,24 @@ class MessagesPreparator constructor(
|
||||
)
|
||||
|
||||
if (message.isPeerChat()) {
|
||||
|
||||
val fromDiffSender = VkUtils.isPreviousMessageFromDifferentSender(prevMessage, message)
|
||||
val prevSenderDiff = VkUtils.isPreviousMessageFromDifferentSender(prevMessage, message)
|
||||
val nextSenderDiff = VkUtils.isPreviousMessageFromDifferentSender(message, nextMessage)
|
||||
val fiveMinAgo = VkUtils.isPreviousMessageSentFiveMinutesAgo(prevMessage, message)
|
||||
|
||||
val change = (prevMessage?.date ?: 0) - message.date
|
||||
|
||||
Log.d(
|
||||
"Fast::MessagesPreparator",
|
||||
"text: ${message.text}; prevText: ${prevMessage?.text}; time change: $change; fromDiffSender: $fromDiffSender; fiveMinAgo: $fiveMinAgo; "
|
||||
"text: ${message.text}; prevText: ${prevMessage?.text}; time change: $change; fromDiffSender: $prevSenderDiff; fiveMinAgo: $fiveMinAgo; "
|
||||
)
|
||||
|
||||
title?.isVisible = fromDiffSender || fiveMinAgo
|
||||
title?.isVisible = prevSenderDiff || fiveMinAgo
|
||||
|
||||
avatar?.isInvisible = fromDiffSender && fiveMinAgo
|
||||
avatar?.visibility =
|
||||
if (nextSenderDiff
|
||||
|| (fiveMinAgo && prevSenderDiff)
|
||||
|| (!prevSenderDiff && nextMessage == null)
|
||||
) View.VISIBLE else View.INVISIBLE
|
||||
} else {
|
||||
title?.isVisible = false
|
||||
avatar?.isVisible = false
|
||||
@@ -131,7 +137,6 @@ class MessagesPreparator constructor(
|
||||
}
|
||||
|
||||
title.text = titleString
|
||||
title.measure(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,13 +169,16 @@ class MessagesPreparator constructor(
|
||||
attachmentContainer.removeAllViews()
|
||||
} else {
|
||||
attachmentContainer.isVisible = true
|
||||
|
||||
AttachmentInflater(
|
||||
context = context,
|
||||
container = attachmentContainer,
|
||||
message = message,
|
||||
groups = groups,
|
||||
profiles = profiles
|
||||
).inflate()
|
||||
)
|
||||
.setPhotoClickListener(photoClickListener)
|
||||
.inflate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.meloda.fast.screens.photos
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.meloda.fast.base.BaseViewModelFragment
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class PhotoViewFragment : BaseViewModelFragment<PhotoViewViewModel>() {
|
||||
|
||||
override val viewModel: PhotoViewViewModel by viewModels()
|
||||
|
||||
// private val photosList: MutableList<VkPhoto> = mutableListOf()
|
||||
|
||||
private var photoLink: String? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
photoLink = requireArguments().getString("photoLink")
|
||||
|
||||
// val list: List<*>? = Gson().fromJson(
|
||||
// requireArguments().getString("photosList"),
|
||||
// List::class.java
|
||||
// )
|
||||
//
|
||||
// list?.forEach { if (it is VkPhoto) photosList.add(it) }
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return ImageView(requireContext())
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
photoLink?.let { viewModel.loadImageFromUrl(it, requireView() as ImageView) }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.meloda.fast.screens.photos
|
||||
|
||||
import android.widget.ImageView
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import coil.load
|
||||
import com.meloda.fast.base.viewmodel.BaseViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PhotoViewViewModel : BaseViewModel() {
|
||||
|
||||
fun loadImageFromUrl(
|
||||
url: String,
|
||||
imageView: ImageView
|
||||
) = viewModelScope.launch {
|
||||
imageView.load(url)
|
||||
}
|
||||
|
||||
fun saveImageToLocalStorage(url: String) = viewModelScope.launch {
|
||||
TODO("Not implemented")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import java.util.*
|
||||
|
||||
object TimeUtils {
|
||||
|
||||
const val ONE_DAY_IN_SECONDS = 86400
|
||||
|
||||
fun removeTime(date: Date): Long {
|
||||
return Calendar.getInstance().apply {
|
||||
time = date
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M9,16.2l-3.5,-3.5c-0.39,-0.39 -1.01,-0.39 -1.4,0 -0.39,0.39 -0.39,1.01 0,1.4l4.19,4.19c0.39,0.39 1.02,0.39 1.41,0L20.3,7.7c0.39,-0.39 0.39,-1.01 0,-1.4 -0.39,-0.39 -1.01,-0.39 -1.4,0L9,16.2z" />
|
||||
</vector>
|
||||
@@ -219,8 +219,10 @@
|
||||
android:minHeight="105dp"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:visibility="gone"
|
||||
app:layout_anchor="@+id/messagePanel"
|
||||
app:layout_anchorGravity="center_vertical|top">
|
||||
app:layout_anchorGravity="center_vertical|top"
|
||||
tools:visibility="visible">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/replyMessage"
|
||||
@@ -249,10 +251,9 @@
|
||||
android:id="@+id/dismissReply"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/ic_image_button_circle_background"
|
||||
android:backgroundTint="@color/n1_50"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_round_close_20"
|
||||
android:tint="?colorSecondary3" />
|
||||
android:tint="@color/n1_800" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
|
||||
@@ -70,19 +70,6 @@
|
||||
android:textColor="@color/n1_800"
|
||||
tools:text="This" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/attachmentSpacer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="5dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/attachmentContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</com.meloda.fast.widget.BoundedLinearLayout>
|
||||
@@ -96,6 +83,20 @@
|
||||
android:src="@color/a3_200" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<Space
|
||||
android:id="@+id/attachmentSpacer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="5dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/attachmentContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
</layout>
|
||||
@@ -1,6 +1,5 @@
|
||||
<?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"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
@@ -35,44 +34,36 @@
|
||||
android:id="@+id/bubble"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_gravity="end"
|
||||
android:background="@drawable/ic_message_out_background"
|
||||
android:clipChildren="true"
|
||||
android:clipToPadding="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|start"
|
||||
android:padding="15dp"
|
||||
android:textColor="@color/n1_900"
|
||||
tools:text="This is test" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|start"
|
||||
android:padding="15dp"
|
||||
android:textColor="@color/n1_900"
|
||||
tools:text="This is test" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/attachmentSpacer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="5dp"
|
||||
android:layout_below="@+id/text"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/attachmentContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/attachmentSpacer"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_anchor="@+id/text"
|
||||
app:layout_anchorGravity="bottom" />
|
||||
|
||||
</RelativeLayout>
|
||||
</com.meloda.fast.widget.BoundedLinearLayout>
|
||||
|
||||
<Space
|
||||
android:id="@+id/attachmentSpacer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="5dp"
|
||||
android:layout_below="@+id/text"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/attachmentContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone" />
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
</layout>
|
||||
@@ -21,6 +21,17 @@
|
||||
android:id="@+id/messagesHistoryFragment"
|
||||
android:name="com.meloda.fast.screens.messages.MessagesHistoryFragment"
|
||||
android:label="MessagesHistoryFragment"
|
||||
tools:layout="@layout/fragment_messages_history" />
|
||||
tools:layout="@layout/fragment_messages_history">
|
||||
|
||||
<action
|
||||
android:id="@+id/toPhotoView"
|
||||
app:destination="@+id/photoViewFragment" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/photoViewFragment"
|
||||
android:name="com.meloda.fast.screens.photos.PhotoViewFragment"
|
||||
android:label="PhotoViewFragment" />
|
||||
|
||||
</navigation>
|
||||
@@ -117,4 +117,8 @@
|
||||
<string name="message_context_action_reply">Reply</string>
|
||||
<string name="message_context_action_mark_as_important">Mark as important</string>
|
||||
<string name="message_context_action_unmark_as_important">Unmark as important</string>
|
||||
<string name="time_format">Time: %s</string>
|
||||
<string name="message_context_action_pin">Pin</string>
|
||||
<string name="message_context_action_unpin">Unpin</string>
|
||||
<string name="message_context_action_edit">Edit</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user